@bongos/core 1.19.456 → 1.19.458
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 +40 -30
- package/docs/copy-inventory.md +160 -177
- package/docs/copy-registry.json +445 -606
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/gate.css +20 -88
- package/modules/hall-ui/public/gate.html +24 -12
- package/modules/hall-ui/public/gate.js +77 -60
- package/modules/hall-ui/public/gate.states.json +19 -1
- package/modules/hall-ui/public/government.css +96 -154
- package/modules/hall-ui/public/government.html +80 -52
- package/modules/hall-ui/public/government.js +227 -136
- package/modules/hall-ui/public/government.states.json +71 -1
- package/modules/hall-ui/public/harbor.css +9 -120
- package/modules/hall-ui/public/harbor.html +23 -11
- package/modules/hall-ui/public/harbor.js +109 -83
- package/modules/hall-ui/public/harbor.states.json +19 -1
- package/modules/hall-ui/public/oversight.css +296 -0
- package/modules/hall-ui/public/watch.css +56 -170
- package/modules/hall-ui/public/watch.html +48 -37
- package/modules/hall-ui/public/watch.js +363 -298
- package/modules/hall-ui/public/watch.states.json +48 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/hall_oversight_world.mjs +118 -0
package/docs/file-map.md
CHANGED
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
│ ├── sessions/ ← CORE-DOMAIN carve (BV1.R82, default-on): the session_records corpus + data plane (upload, cross-builder search/deep-dive), the BFG read-the-room scorer, session-pulse/stale-claim helpers. Registers no port — reached via HTTP POST to its own /sessions route; resolves the economy `reward` port for the cost-plus token reward. CLAUDE.md, module.json, routes/sessions.js, bfg.js, pulse.js.
|
|
128
128
|
│ ├── autonomy/ ← CORE-DOMAIN carve (BV1.R83, default-on): the MAS autonomy surfaces — the $0 deterministic autonomy gate (System 3, inert until armed), the conductor/architect CLIs, and the in-process routine timers (incl. the weekly peer-vote-tally poller). Registers no port; resolves the lifecycle `tallyPeerVotes` doorway. CLAUDE.md, module.json, routes/autonomy.js, autonomy.js.
|
|
129
129
|
│ ├── status-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R84, default-on): the public status dashboard at status.<apex>, moved from public-status/. NO routes/db/port — declares contributes.webSurfaces:[{host:"status.",dir:"public"}], served by the loader web-surface seam (moduleWebSurfaces → serve-internal). CLAUDE.md, module.json, public/{index.html,status.js,style.css,cursors/,snapshots/}.
|
|
130
|
-
│ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav.
|
|
130
|
+
│ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav. Three ARCHETYPE sheets sit beside the per-page ones: `record.css` (the task + idea records, task 1003305), `room.css` (the READING ROOMS — the primer + the diagrams index, task 1003306: the reading measure and rhythm, the table of contents, the progress hairline and the framed figure diagram-viewer.js upgrades on both pages) and `oversight.css` (the OVERSIGHT family — government, watch, harbor, gate, task 1003469: the row, the figure band inside a panel, the group head, the inline form, the grant toggle and the shared confirm modal; named oversight because the vocabulary wall bans "governance" on the government surface). A page sheet then holds only what one page has and the others do not — `primer.css` the acknowledge gate, `diagrams.css` the legend strip, `gate.css` the hold reasons and file chips, `watch.css` the severities, the report reader and the grade trend.
|
|
131
131
|
│ ├── lifecycle/ ← CORE-DOMAIN carve (BV1.R86, default-on): the build state machine — tasks · claims · versions · done-when · goals · dependencies · claim→ship→grade→credit · github-push + merge-lock + conflict-resolve + publish-reconciler. The LAST, most-coupled carve; registers the `lifecycle` kernel port (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads). CLAUDE.md, module.json, routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js, lifecycle.js, github-push.js, merge-lock.js, conflict-resolve.js, publish-reconciler.js, task-visuals.js + task-visual-db.js + routes/visuals.js (the optional ship-time visual, task 1003109 — the route file is NOT named task-*.js on purpose; see the module CLAUDE.md).
|
|
132
132
|
│ ├── copy-desk/ ← CORE-DOMAIN module (task 1003113 / R02 of goal 1000074, default-on): the COPY half of the design contract — ADR 0081 gave design TOKENS a repo-owned source of truth, text had none. Owns the FLAG (anyone in any role marks a string or a whole surface as needing an artist's attention, with a reason required in BOTH the route and a CHECK constraint) and the QUEUE an artist reads it from. READS R01's committed registry (docs/copy-registry.json, generated by scripts/gds/copy-inventory.js) and never writes it. NO LIVE CMS is the hard non-goal: no column here could hold replacement text, and copy_no_cms.mjs pins that. Key is `copy-desk`, not `copy` — the branding contract already owns a `copy` block, so the bare word reds the "core carries no module code" fitness check. CLAUDE.md, module.json, registry.js, flags.js, routes/copy-desk.js, migrations/copy_desk_001_flags.sql, tests/{copy_flags,copy_queue,copy_no_cms}.mjs. Its hall surface lives with hall-ui (public/copy-desk.{html,js,css}), per the one-key-per-web-surface precedent.
|
|
133
133
|
│ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. Reserved home styles/ (1003323) with a README. config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
|
|
@@ -1349,5 +1349,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1349
1349
|
landed since 1.19.454 with no explicit bump. run 33558738299. (task 1002620)
|
|
1350
1350
|
1.19.456 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1351
1351
|
landed since 1.19.455 with no explicit bump. run 33560584235. (task 1002620)
|
|
1352
|
+
1.19.457 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1353
|
+
landed since 1.19.456 with no explicit bump. run 33561330390. (task 1002620)
|
|
1354
|
+
1.19.458 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1355
|
+
landed since 1.19.457 with no explicit bump. run 33566876510. (task 1002620)
|
|
1352
1356
|
---------------------------------------------------------------------------
|
|
1353
1357
|
```
|
|
@@ -1,93 +1,25 @@
|
|
|
1
|
-
/* gate.css — Page-local styles for the Gate
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* gate.css — Page-local styles for the Gate (task 1003469 rebuild).
|
|
2
|
+
One panel of oversight rows, one held pull request per row, over the shell's
|
|
3
|
+
glass summary strip; everything structural is oversight.css. What is the
|
|
4
|
+
gate's own: the hold reasons (a hard floor reads in the danger ink), and the
|
|
5
|
+
changed-file run. The ?v= cache-bust is stamped from this file's content hash
|
|
4
6
|
at boot (src/asset-version.js). */
|
|
5
7
|
|
|
6
|
-
/*
|
|
7
|
-
on the
|
|
8
|
-
|
|
9
|
-
.gate-
|
|
10
|
-
|
|
11
|
-
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
12
|
-
gap: 14px;
|
|
13
|
-
margin: 6px 0 22px;
|
|
14
|
-
}
|
|
15
|
-
.gate-stat {
|
|
16
|
-
border: var(--line) solid var(--rule-soft); border-radius: var(--radius-sm);
|
|
17
|
-
background: var(--bg-card); padding: 12px 14px;
|
|
18
|
-
}
|
|
19
|
-
.gate-stat__label {
|
|
20
|
-
font-size: 11.5px; font-weight: 600;
|
|
21
|
-
letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft);
|
|
22
|
-
}
|
|
23
|
-
.gate-stat__value {
|
|
24
|
-
font-size: 1.6rem; font-weight: 600; color: var(--ink);
|
|
25
|
-
font-variant-numeric: tabular-nums; margin-top: 2px;
|
|
26
|
-
}
|
|
8
|
+
/* A hold reason: the path in the machine's face, the reason beside it. A hard
|
|
9
|
+
floor is the danger ink on the path and a name on the line, never a flag glyph. */
|
|
10
|
+
.gate-reason .ov-key { max-width: 40%; }
|
|
11
|
+
.gate-reason--floor .ov-key { color: var(--danger); }
|
|
12
|
+
.gate-reason__floor { font-family: var(--label-face); font-size: 10.5px; letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--danger); white-space: nowrap; }
|
|
27
13
|
|
|
28
|
-
|
|
14
|
+
/* The changed files: one chip per path, wrapping, the overflow named as a count. */
|
|
15
|
+
.gate-files { display: flex; flex-wrap: wrap; gap: 6px 8px; margin: 12px 0 0; }
|
|
16
|
+
.gate-files .fact-chip { text-transform: none; letter-spacing: normal; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
|
|
17
|
+
.gate-files__more { align-self: center; font-size: 12px; color: var(--ink-faint); }
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.gate-
|
|
36
|
-
.gate-crate--needs_trust .gate-crate__seal { background-color: var(--warn); }
|
|
37
|
-
.gate-crate__body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
38
|
-
.gate-crate__head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
|
|
39
|
-
.gate-crate__title { font-weight: 600; color: var(--ink); font-size: 15px; }
|
|
40
|
-
.gate-crate__title a { color: inherit; text-decoration: none; }
|
|
41
|
-
.gate-crate__title a:hover { text-decoration: underline; }
|
|
42
|
-
.gate-crate__meta { color: var(--ink-soft); font-size: 12.5px; }
|
|
43
|
-
.gate-crate__meta a { color: var(--accent-ink); }
|
|
44
|
-
|
|
45
|
-
/* Base: the shell's .fact-pill (task 1003307). States are hairlines, not fills. */
|
|
46
|
-
.gate-pill--escalate { color: var(--danger); }
|
|
47
|
-
.gate-pill--needs_trust { color: var(--warn); }
|
|
48
|
-
.gate-pill--trusted { color: var(--info); }
|
|
49
|
-
|
|
50
|
-
.gate-reasons { list-style: none; padding: 0; margin: 0; }
|
|
51
|
-
.gate-reasons li {
|
|
52
|
-
padding: 6px 0; border-bottom: var(--line) solid var(--rule-soft);
|
|
53
|
-
font-size: 13px; color: var(--ink);
|
|
54
|
-
}
|
|
55
|
-
.gate-reasons li:last-child { border-bottom: 0; }
|
|
56
|
-
.gate-reasons code {
|
|
57
|
-
font-family: var(--font-mono); font-size: 12px;
|
|
58
|
-
color: var(--accent-ink); background: var(--glass);
|
|
59
|
-
padding: 1px 5px; border-radius: 4px;
|
|
60
|
-
}
|
|
61
|
-
.gate-reasons .floor { color: var(--danger); font-weight: 600; }
|
|
62
|
-
|
|
63
|
-
.gate-files { font-size: 12.5px; color: var(--ink-soft); }
|
|
64
|
-
.gate-files code { font-family: var(--font-mono); }
|
|
65
|
-
|
|
66
|
-
.gate-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 2px; }
|
|
67
|
-
.gate-btn {
|
|
68
|
-
font: inherit; font-size: 13px; font-weight: 600;
|
|
69
|
-
padding: 7px 16px; border: var(--line) solid var(--accent);
|
|
70
|
-
background: var(--accent); color: var(--accent-on);
|
|
71
|
-
border-radius: var(--radius-sm); cursor: pointer;
|
|
72
|
-
}
|
|
73
|
-
.gate-btn:hover:not(:disabled) { background: var(--accent-lift); border-color: var(--accent-lift); }
|
|
74
|
-
.gate-btn:disabled { opacity: 0.45; cursor: default; }
|
|
75
|
-
.gate-btn--ghost { background: var(--bg-card); color: var(--ink); border-color: var(--rule); }
|
|
76
|
-
.gate-btn--ghost:hover:not(:disabled) { background: var(--glass); border-color: var(--rule); }
|
|
77
|
-
.gate-approved-note { color: var(--ok); font-size: 13px; }
|
|
78
|
-
|
|
79
|
-
.gate-empty, .gate-sealed { color: var(--ink-soft); padding: 16px 0; }
|
|
80
|
-
.gate-checking { color: var(--ink-faint); font-size: 12.5px; margin-top: 10px; }
|
|
81
|
-
|
|
82
|
-
/* Lightweight confirm modal (mirrors harbor). */
|
|
83
|
-
.gate-modal-bg {
|
|
84
|
-
position: fixed; inset: 0; background: color-mix(in srgb, var(--ink) 40%, transparent);
|
|
85
|
-
display: flex; align-items: center; justify-content: center; z-index: var(--z-modal); padding: 20px;
|
|
86
|
-
}
|
|
87
|
-
.gate-modal {
|
|
88
|
-
background: var(--bg-card); border: var(--line) solid var(--rule); border-radius: var(--radius);
|
|
89
|
-
max-width: 480px; width: 100%; padding: 20px 22px; box-shadow: var(--shadow-pop);
|
|
19
|
+
/* On a phone the path takes the line with its floor label at the right, and the
|
|
20
|
+
reason drops under it in full width. */
|
|
21
|
+
@media (max-width: 640px) {
|
|
22
|
+
.gate-reason .ov-key { max-width: none; }
|
|
23
|
+
.gate-reason > span:nth-of-type(2) { flex: 1 1 100%; order: 3; }
|
|
24
|
+
.gate-reason__floor { margin-left: auto; }
|
|
90
25
|
}
|
|
91
|
-
.gate-modal h2 { color: var(--ink); margin: 0 0 8px; font-size: 17px; font-weight: 600; }
|
|
92
|
-
.gate-modal p { color: var(--ink); font-size: 13.5px; margin: 0 0 12px; }
|
|
93
|
-
.gate-modal__actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; }
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
12
|
+
<link rel="stylesheet" href="/builders/oversight.css">
|
|
12
13
|
<link rel="stylesheet" href="/builders/gate.css">
|
|
13
14
|
</head>
|
|
14
15
|
<body data-page="gate" data-page-title="Gate">
|
|
@@ -19,25 +20,35 @@
|
|
|
19
20
|
<div class="app-body">
|
|
20
21
|
<header class="app-topbar" id="app-topbar"></header>
|
|
21
22
|
<div class="app-content">
|
|
22
|
-
<!--
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
is the LABEL voice, not a display line: on a working surface the
|
|
26
|
-
page's own name is the least interesting thing on it. -->
|
|
23
|
+
<!-- The board page head (task 1003307): the LABEL voice, not a display
|
|
24
|
+
line, because on a working surface the page's own name is the least
|
|
25
|
+
interesting thing on it. -->
|
|
27
26
|
<div class="page-head page-head--board">
|
|
28
27
|
<h1 class="page-head__h">Gate</h1>
|
|
29
28
|
<p class="page-head__sub" id="gate-sub">Loading…</p>
|
|
30
29
|
</div>
|
|
31
30
|
|
|
32
31
|
<main id="hall-main">
|
|
33
|
-
|
|
32
|
+
<!-- task 1003469: the sealed state is a panel with a heading, not a bare
|
|
33
|
+
line on the ground. gate.js writes the real floor into the lede from
|
|
34
|
+
the pack's rank label, so the copy cannot drift from ADR 0157. -->
|
|
35
|
+
<section class="scroll" id="gate-sealed" aria-labelledby="gate-sealed-h" hidden>
|
|
36
|
+
<h2 class="scroll__h" id="gate-sealed-h">Restricted</h2>
|
|
37
|
+
<p class="scroll__lede" id="gate-sealed-body">This page opens at a higher rank.</p>
|
|
38
|
+
</section>
|
|
34
39
|
|
|
35
40
|
<section id="gate-body" hidden>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
<!-- The glass summary strip (the shell's .stat-row), then ONE panel of
|
|
42
|
+
oversight rows, one held pull request each — the bordered crate
|
|
43
|
+
with a verdict band along its top is gone (task 1003469). -->
|
|
44
|
+
<div class="stat-row ov-summary" id="gate-summary" aria-label="Held changes summary"></div>
|
|
45
|
+
|
|
46
|
+
<section class="scroll" aria-labelledby="gate-list-h">
|
|
47
|
+
<h2 class="scroll__h" id="gate-list-h">Held pull requests</h2>
|
|
48
|
+
<p class="scroll__lede">Each one touched a protected surface and stopped at the gate check. Approving records your seal on that commit and re-runs the check.</p>
|
|
49
|
+
<div id="gate-list"><span class="kit-skel"></span></div>
|
|
50
|
+
<p class="ov-foot" id="gate-checking" hidden></p>
|
|
51
|
+
</section>
|
|
41
52
|
</section>
|
|
42
53
|
</main>
|
|
43
54
|
|
|
@@ -53,6 +64,7 @@
|
|
|
53
64
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
54
65
|
<script src="/builders/palette.js" defer></script>
|
|
55
66
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
56
|
-
<script src="/builders/
|
|
67
|
+
<script src="/builders/hall-kit.js"></script>
|
|
68
|
+
<script src="/builders/gate.js?v=2026-09-02-gate-rows"></script>
|
|
57
69
|
</body>
|
|
58
70
|
</html>
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// Archon-only approval view for gate-surface PRs (task 1179, ADR 0058 amendment).
|
|
4
4
|
// A pull request that touches the build's protected machinery is held by the
|
|
5
5
|
// gate-review CI check until the owner approves. This page lists every such PR
|
|
6
|
-
// with WHY it was stopped (the same
|
|
7
|
-
// button that records the owner's seal
|
|
8
|
-
// and re-runs the check so the PR merges.
|
|
6
|
+
// as one oversight row (task 1003469) with WHY it was stopped (the same
|
|
7
|
+
// classifier the CI runs), and an Approve button that records the owner's seal
|
|
8
|
+
// (a server-set gate-owner-approved status) and re-runs the check so the PR merges.
|
|
9
9
|
//
|
|
10
10
|
// Like The Watch + The Harbor, this reads /api/bongos/me to show a Sealed notice for
|
|
11
11
|
// non-Archons — DEFENCE IN DEPTH only. GET /gate-approvals + POST
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
// through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
|
22
22
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
23
23
|
const { escapeHtml, fmtInt } = window.OTB;
|
|
24
|
+
const { emptyStateHtml } = window.OTBKit;
|
|
25
|
+
const rankLabel = (rank) => window.OTB.rankLabel(rank, { empty: '' });
|
|
24
26
|
const toast = (msg) => window.OTB.toast(msg);
|
|
25
27
|
|
|
26
28
|
// ---- fetch helpers (mirror harbor.js / watch.js) -------------------------
|
|
@@ -46,8 +48,10 @@
|
|
|
46
48
|
const shortSha = (s) => (typeof s === 'string' ? s.slice(0, 7) : '');
|
|
47
49
|
const VERDICT_LABEL = { escalate: 'Hard floor', needs_trust: 'Needs trust', pass: 'Clear' };
|
|
48
50
|
const verdictLabel = (v) => VERDICT_LABEL[v] || (v || 'held');
|
|
51
|
+
// How many files a row shows before it names the rest as a count.
|
|
52
|
+
const FILES_SHOWN = 12;
|
|
49
53
|
|
|
50
|
-
// ---- modal (
|
|
54
|
+
// ---- modal (the oversight family's, oversight.css) -----------------------
|
|
51
55
|
|
|
52
56
|
function closeModal() {
|
|
53
57
|
const root = document.getElementById('gate-modal-root');
|
|
@@ -58,20 +62,23 @@
|
|
|
58
62
|
const root = document.getElementById('gate-modal-root');
|
|
59
63
|
root.innerHTML = '';
|
|
60
64
|
const bg = document.createElement('div');
|
|
61
|
-
bg.className = '
|
|
65
|
+
bg.className = 'ov-modal-bg';
|
|
62
66
|
bg.innerHTML =
|
|
63
|
-
`<div class="
|
|
64
|
-
<h2>${escapeHtml(title)}</h2>
|
|
67
|
+
`<div class="ov-modal" role="dialog" aria-modal="true" aria-labelledby="gate-modal-h">
|
|
68
|
+
<h2 class="scroll__h" id="gate-modal-h">${escapeHtml(title)}</h2>
|
|
65
69
|
${bodyHtml}
|
|
66
|
-
<div class="
|
|
67
|
-
<button class="
|
|
68
|
-
<button class="
|
|
70
|
+
<div class="ov-modal__actions">
|
|
71
|
+
<button type="button" class="btn-ghost" data-act="cancel">Cancel</button>
|
|
72
|
+
<button type="button" class="btn-accent" data-act="confirm">${escapeHtml(confirmLabel)}</button>
|
|
69
73
|
</div>
|
|
70
74
|
</div>`;
|
|
71
75
|
bg.addEventListener('click', (e) => { if (e.target === bg) closeModal(); });
|
|
76
|
+
bg.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
|
|
72
77
|
bg.querySelector('[data-act=cancel]').addEventListener('click', closeModal);
|
|
73
78
|
bg.querySelector('[data-act=confirm]').addEventListener('click', () => onConfirm(bg));
|
|
74
79
|
root.appendChild(bg);
|
|
80
|
+
const confirm = bg.querySelector('[data-act=confirm]');
|
|
81
|
+
if (confirm) confirm.focus();
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
// ---- actions -------------------------------------------------------------
|
|
@@ -79,13 +86,13 @@
|
|
|
79
86
|
function confirmApprove(pr) {
|
|
80
87
|
const floors = (pr.reasons || []).filter((r) => r.hard_floor).map((r) => r.path);
|
|
81
88
|
const what = floors.length
|
|
82
|
-
? `the hard-floor surface ${floors.slice(0, 3).map((p) => `<
|
|
89
|
+
? `the hard-floor surface ${floors.slice(0, 3).map((p) => `<span class="ov-key">${escapeHtml(p)}</span>`).join(', ')}`
|
|
83
90
|
: 'a protected build surface';
|
|
84
91
|
openModal({
|
|
85
92
|
title: `Approve PR #${pr.number}?`,
|
|
86
93
|
bodyHtml:
|
|
87
94
|
`<p>Record your owner approval for <strong>${escapeHtml(pr.title || `PR #${pr.number}`)}</strong>${pr.task ? ` (task ${pr.task})` : ''}.</p>
|
|
88
|
-
<p>This clears ${what}. The gate check re-runs and the PR merges once it goes green. Your approval applies only to commit <
|
|
95
|
+
<p>This clears ${what}. The gate check re-runs and the PR merges once it goes green. Your approval applies only to commit <span class="ov-key">${escapeHtml(shortSha(pr.head_sha))}</span>; a later push must be re-approved.</p>`,
|
|
89
96
|
confirmLabel: 'Approve & re-run',
|
|
90
97
|
onConfirm: async (modal) => {
|
|
91
98
|
const btn = modal.querySelector('[data-act=confirm]');
|
|
@@ -106,66 +113,70 @@
|
|
|
106
113
|
|
|
107
114
|
// ---- render --------------------------------------------------------------
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
// One held PR, one oversight row (oversight.css .ov-row). This is the page
|
|
117
|
+
// that shows the most facts in the hall — title, verdict, trust, task, PR,
|
|
118
|
+
// author, sha, diff, every hold reason and the changed files — and every one
|
|
119
|
+
// of them survives the rebuild: the verdict is a state pill instead of a band,
|
|
120
|
+
// the reasons are hairline detail lines, the files are chips.
|
|
121
|
+
function renderRow(pr) {
|
|
122
|
+
const floor = pr.verdict !== 'needs_trust';
|
|
123
|
+
const reasons = (pr.reasons || []).map((r) =>
|
|
124
|
+
`<li class="gate-reason${r.hard_floor ? ' gate-reason--floor' : ''}">` +
|
|
125
|
+
`<span class="ov-key">${escapeHtml(r.path)}</span>` +
|
|
126
|
+
`<span>${escapeHtml(r.reason)}</span>` +
|
|
127
|
+
(r.hard_floor ? '<span class="gate-reason__floor">hard floor</span>' : '') +
|
|
128
|
+
'</li>').join('')
|
|
129
|
+
|| '<li><span>This PR is held by the gate-review check.</span></li>';
|
|
114
130
|
|
|
115
131
|
const files = pr.files || [];
|
|
116
|
-
const
|
|
117
|
-
|
|
132
|
+
const chips = files.slice(0, FILES_SHOWN).map((f) =>
|
|
133
|
+
`<span class="fact-chip" title="${escapeHtml(f.path)}">${escapeHtml(f.path)}</span>`).join('');
|
|
134
|
+
const more = files.length > FILES_SHOWN ? `<span class="gate-files__more">+${files.length - FILES_SHOWN} more</span>` : '';
|
|
118
135
|
const filesHtml = files.length
|
|
119
|
-
? `<
|
|
136
|
+
? `<div class="gate-files" aria-label="Changed files"><span class="ov-fact">${fmtInt(files.length)} file${files.length === 1 ? '' : 's'}</span>${chips}${more}</div>`
|
|
120
137
|
: '';
|
|
121
138
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
: ''
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
const facts = [
|
|
140
|
+
`<span class="fact-pill fact-pill--${floor ? 'danger' : 'warn'}">${escapeHtml(verdictLabel(pr.verdict))}</span>`,
|
|
141
|
+
pr.author_trusted ? '<span class="fact-pill fact-pill--info">author trusted</span>' : '',
|
|
142
|
+
pr.task ? `<span class="ov-fact"><a href="/#/task/${encodeURIComponent(pr.task)}">task ${escapeHtml(String(pr.task))}</a></span>` : '',
|
|
143
|
+
`<span class="ov-fact">by <b>${escapeHtml(pr.author || 'unknown')}</b></span>`,
|
|
144
|
+
`<span class="ov-mono">${escapeHtml(shortSha(pr.head_sha))}</span>`,
|
|
145
|
+
`<span class="ov-fact"><a href="${escapeHtml(pr.url)}/files" target="_blank" rel="noopener">view diff</a></span>`,
|
|
146
|
+
].filter(Boolean).join('');
|
|
147
|
+
|
|
148
|
+
const action = pr.already_approved
|
|
149
|
+
? '<span class="ov-fact ov-ok">Approved. Re-check pending.</span>'
|
|
150
|
+
: '<button type="button" class="btn-accent" data-act="approve">Approve</button>';
|
|
151
|
+
|
|
152
|
+
const el = document.createElement('li');
|
|
153
|
+
el.className = 'ov-row';
|
|
134
154
|
el.innerHTML =
|
|
135
|
-
`<div class="
|
|
136
|
-
|
|
137
|
-
<div class="
|
|
138
|
-
|
|
139
|
-
<span>
|
|
140
|
-
<span class="gate-pill gate-pill--${mod}">${escapeHtml(verdictLabel(pr.verdict))}</span>
|
|
141
|
-
${trustedPill}
|
|
142
|
-
</span>
|
|
143
|
-
</div>
|
|
144
|
-
<p class="gate-crate__meta">
|
|
145
|
-
${taskHtml}<a href="${escapeHtml(pr.url)}" target="_blank" rel="noopener">#${escapeHtml(String(pr.number))}</a>
|
|
146
|
-
· by ${escapeHtml(pr.author || 'unknown')} · <code>${escapeHtml(shortSha(pr.head_sha))}</code>
|
|
147
|
-
· <a href="${escapeHtml(pr.url)}/files" target="_blank" rel="noopener">view diff ↗</a>
|
|
148
|
-
</p>
|
|
149
|
-
<ul class="gate-reasons">${reasonsHtml}</ul>
|
|
155
|
+
`<div class="ov-row__main">
|
|
156
|
+
<a class="ov-row__title" href="${escapeHtml(pr.url)}" target="_blank" rel="noopener">${escapeHtml(pr.title || `PR #${pr.number}`)}<span class="ov-mono">#${escapeHtml(String(pr.number))}</span></a>
|
|
157
|
+
<div class="ov-row__facts">${facts}</div>
|
|
158
|
+
<ul class="ov-lines">${reasons}</ul>
|
|
150
159
|
${filesHtml}
|
|
151
|
-
|
|
152
|
-
</div>`;
|
|
160
|
+
</div>
|
|
161
|
+
<div class="ov-row__right">${action}</div>`;
|
|
153
162
|
|
|
154
163
|
const approveBtn = el.querySelector('[data-act=approve]');
|
|
155
164
|
if (approveBtn) approveBtn.addEventListener('click', () => confirmApprove(pr));
|
|
156
165
|
return el;
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
// The glass strip above the panel: the shell's .stat-row, each tile a figure
|
|
169
|
+
// with the one clause that makes it a fact rather than a number.
|
|
159
170
|
function renderSummary(awaiting) {
|
|
160
171
|
const floors = awaiting.filter((p) => p.verdict === 'escalate').length;
|
|
161
172
|
const trust = awaiting.filter((p) => p.verdict === 'needs_trust').length;
|
|
162
|
-
const
|
|
163
|
-
['Awaiting your approval', fmtInt(awaiting.length)],
|
|
164
|
-
['Hard-floor holds', fmtInt(floors)],
|
|
165
|
-
['Trust holds', fmtInt(trust)],
|
|
173
|
+
const tiles = [
|
|
174
|
+
['Awaiting your approval', fmtInt(awaiting.length), 'held at the gate check'],
|
|
175
|
+
['Hard-floor holds', fmtInt(floors), 'touch a surface only an owner may clear'],
|
|
176
|
+
['Trust holds', fmtInt(trust), 'an author the gate does not yet trust'],
|
|
166
177
|
];
|
|
167
|
-
document.getElementById('gate-summary').innerHTML =
|
|
168
|
-
`<div class="
|
|
178
|
+
document.getElementById('gate-summary').innerHTML = tiles.map(([label, num, sub]) =>
|
|
179
|
+
`<div class="stat-tile"><span class="stat-tile__label">${escapeHtml(label)}</span><span class="stat-tile__num">${num}</span><span class="stat-tile__sub">${escapeHtml(sub)}</span></div>`).join('');
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
async function load() {
|
|
@@ -177,7 +188,7 @@
|
|
|
177
188
|
} catch (err) {
|
|
178
189
|
if (err && err.kind === '401') return;
|
|
179
190
|
if (err && err.kind === '403') { showSealed(); return; }
|
|
180
|
-
list.innerHTML =
|
|
191
|
+
list.innerHTML = emptyStateHtml('Could not load held pull requests just now.');
|
|
181
192
|
return;
|
|
182
193
|
}
|
|
183
194
|
|
|
@@ -185,15 +196,18 @@
|
|
|
185
196
|
renderSummary(awaiting);
|
|
186
197
|
|
|
187
198
|
if (awaiting.length === 0) {
|
|
188
|
-
list.innerHTML =
|
|
199
|
+
list.innerHTML = emptyStateHtml('Nothing is waiting. No held change needs your approval.');
|
|
189
200
|
} else {
|
|
190
201
|
list.innerHTML = '';
|
|
191
|
-
|
|
202
|
+
const rows = document.createElement('ul');
|
|
203
|
+
rows.className = 'ov-rows';
|
|
204
|
+
for (const pr of awaiting) rows.appendChild(renderRow(pr));
|
|
205
|
+
list.appendChild(rows);
|
|
192
206
|
}
|
|
193
207
|
|
|
194
208
|
if (resp.checking > 0) {
|
|
195
209
|
checkingEl.hidden = false;
|
|
196
|
-
checkingEl.textContent = `${resp.checking} more PR${resp.checking === 1 ? '' : 's'} still being checked
|
|
210
|
+
checkingEl.textContent = `${resp.checking} more PR${resp.checking === 1 ? '' : 's'} still being checked. Refresh shortly.`;
|
|
197
211
|
} else {
|
|
198
212
|
checkingEl.hidden = true;
|
|
199
213
|
}
|
|
@@ -203,10 +217,13 @@
|
|
|
203
217
|
document.getElementById('gate-sealed').hidden = false;
|
|
204
218
|
document.getElementById('gate-body').hidden = true;
|
|
205
219
|
document.getElementById('gate-sub').textContent = 'Restricted.';
|
|
220
|
+
// The floor in the pack's own words (ADR 0157: metic), never a rank spelled into the source.
|
|
221
|
+
const body = document.getElementById('gate-sealed-body');
|
|
222
|
+
if (body) body.textContent = `Available from the ${rankLabel('metic')} rank up.`;
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
async function init() {
|
|
209
|
-
// Confirm rank
|
|
226
|
+
// Confirm the viewer's rank for a friendly sealed notice; the server gates the page
|
|
210
227
|
// + every endpoint regardless (cosmetic defence-in-depth).
|
|
211
228
|
let me;
|
|
212
229
|
try {
|
|
@@ -46,6 +46,23 @@
|
|
|
46
46
|
},
|
|
47
47
|
"out": {
|
|
48
48
|
"auth": false
|
|
49
|
+
},
|
|
50
|
+
"approve": {
|
|
51
|
+
"auth": true,
|
|
52
|
+
"actions": [
|
|
53
|
+
[
|
|
54
|
+
"wait",
|
|
55
|
+
700
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
"clickIfVisible",
|
|
59
|
+
"[data-act=approve]"
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
"wait",
|
|
63
|
+
250
|
|
64
|
+
]
|
|
65
|
+
]
|
|
49
66
|
}
|
|
50
67
|
},
|
|
51
68
|
"reducedMotion": {
|
|
@@ -56,5 +73,6 @@
|
|
|
56
73
|
600
|
|
57
74
|
]
|
|
58
75
|
]
|
|
59
|
-
}
|
|
76
|
+
},
|
|
77
|
+
"_states": "task 1003469: `approve` opens the confirm modal on the first held PR. clickIfVisible, because the kit's own stub carries no /gate-approvals fixture and renders the empty list — the modal is measured against the harness."
|
|
60
78
|
}
|