@bongos/core 1.19.1081 → 1.20.1
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 +526 -191
- package/.claude/skills/planning-session/SKILL.md +6 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +32 -0
- package/clients/bongos-client/index.cjs +32 -0
- package/clients/bongos-client/index.d.ts +51 -6
- package/clients/bongos-client/index.mjs +32 -0
- package/docs/adr/0099-delayed-redacted-mirror-export.md +5 -1
- package/docs/adr/0111-instance-hosting-provisioning-module.md +1 -0
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +17 -0
- package/docs/adr/0176-private-repo-deploy-keys.md +2 -0
- package/docs/adr/0310-a-speciality-offers-skills-and-the-adopter-chooses-them.md +1 -1
- package/docs/adr/0348-the-web-tier-may-look-read-only-at-what-an-owners-render-key-can-see.md +53 -0
- package/docs/adr/0349-a-version-preview-is-a-sandboxed-child-the-web-tier-launches.md +106 -0
- package/docs/adr/0350-the-hub-holds-a-per-project-write-deploy-key-so-a-hosted-upgrade-reaches-github.md +103 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +851 -19
- package/docs/api-reference.md +33 -9
- package/docs/architecture.md +6 -2
- package/docs/copy-inventory.md +620 -553
- package/docs/copy-registry.json +1467 -820
- package/docs/file-map.md +2 -0
- package/docs/module-api-changelog.md +6 -0
- package/docs/modules-contract.md +1 -0
- package/docs/page-inventory.json +38 -4
- package/docs/page-readings.json +1674 -1566
- package/migrations/core_260_goals_working_area.sql +63 -0
- package/migrations/core_261_grade_attempts_verdicts.sql +24 -0
- package/migrations/core_262_drop_builder_box_blocked.sql +33 -0
- package/modules/agents/lib/validate.js +43 -0
- package/modules/autonomy/gauge.js +38 -2
- package/modules/grading/grader-subagent.js +37 -2
- package/modules/grading/grader-workers/reader.js +65 -5
- package/modules/hall-ui/public/approval-queue.css +33 -7
- package/modules/hall-ui/public/approval-queue.js +9 -2
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/brand-holes.js +121 -0
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +6 -1
- package/modules/hall-ui/public/deploy.js +22 -8
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/dom-utils.js +20 -0
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/idea.js +28 -0
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/ideas.js +19 -3
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile-nudge.js +21 -4
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/profile.js +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +19 -30
- package/modules/hall-ui/public/settings.js +24 -177
- package/modules/hall-ui/public/settings.states.json +1 -1
- package/modules/hall-ui/public/shell.js +4 -2
- package/modules/hall-ui/public/studio.css +14 -8
- package/modules/hall-ui/public/studio.html +5 -4
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.css +6 -3
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +82 -1
- package/modules/hall-ui/public/tweak-editor.css +142 -11
- package/modules/hall-ui/public/tweak-editor.html +25 -9
- package/modules/hall-ui/public/tweak-editor.js +243 -10
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/approval-queue.md +6 -0
- package/modules/hall-ui/records/tweak-editor.md +6 -0
- package/modules/ideas/ratify.js +36 -8
- package/modules/ideas/routes/ratify-goal.js +6 -0
- package/modules/lifecycle/db-analytics.js +79 -10
- package/modules/lifecycle/db-claims.js +48 -1
- package/modules/lifecycle/db-deps-criteria.js +94 -40
- package/modules/lifecycle/db-goals.js +4 -1
- package/modules/lifecycle/db-grade.js +9 -1
- package/modules/lifecycle/db-relevance-flags.js +183 -0
- package/modules/lifecycle/db-versions.js +4 -3
- package/modules/lifecycle/db.js +19 -0
- package/modules/lifecycle/done-when.js +5 -3
- package/modules/lifecycle/est-advisory.js +31 -4
- package/modules/lifecycle/goal-task-relevance-advisory.js +153 -0
- package/modules/lifecycle/goal-task-relevance-judge.js +226 -0
- package/modules/lifecycle/migrations/lifecycle_014_goal_task_relevance_flags.sql +82 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/routes/claims.js +8 -1
- package/modules/lifecycle/routes/goal-task-relevance.js +72 -0
- package/modules/lifecycle/routes/goals.js +1 -1
- package/modules/lifecycle/routes/tasks.js +27 -24
- package/modules/lifecycle/routes/visuals.js +9 -0
- package/modules/lifecycle/ship-preflight.js +7 -11
- package/modules/lifecycle/task-visuals.js +109 -5
- package/modules/npm-release/module.json +2 -1
- package/modules/npm-release/preview/commands.js +81 -0
- package/modules/npm-release/preview/divert.js +95 -0
- package/modules/npm-release/preview/env.js +78 -0
- package/modules/npm-release/preview/proxy.js +107 -0
- package/modules/npm-release/preview/runtime.js +83 -0
- package/modules/npm-release/preview/supervisor.js +175 -0
- package/modules/npm-release/public/work.js +65 -1
- package/modules/npm-release/routes/preview.js +133 -0
- package/modules/provisioning/app-status.js +83 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/pollers/app-liveness.js +110 -0
- package/modules/provisioning/render-lookup.js +68 -0
- package/modules/provisioning/render-standup.js +44 -1
- package/modules/provisioning/routes/render-standup.js +81 -6
- package/modules/provisioning/starter-bundles.js +4 -1
- package/modules/public-landing/public/account-private.states.json +14 -0
- package/modules/public-landing/public/account.html +513 -0
- package/modules/public-landing/public/account.probes.json +37 -0
- package/modules/public-landing/public/account.states.json +16 -0
- package/modules/public-landing/public/index.html +5 -2
- package/modules/public-landing/public/projects.html +661 -18
- package/modules/public-landing/public/projects.states.json +8 -1
- package/modules/render-deploy/deploys.js +203 -0
- package/modules/render-deploy/migrations/render_deploy_001_app.sql +22 -0
- package/modules/render-deploy/module.json +25 -0
- package/modules/render-deploy/public/deploy.css +10 -0
- package/modules/render-deploy/public/deploy.js +249 -0
- package/modules/render-deploy/render.js +56 -0
- package/modules/render-deploy/routes/act.js +88 -0
- package/modules/render-deploy/routes/app.js +65 -0
- package/modules/render-deploy/routes/history.js +38 -0
- package/modules/specialities/db.js +27 -8
- package/modules/specialities/migrations/specialities_003_skills.sql +46 -0
- package/modules/specialities/routes/specialities.js +51 -3
- package/modules/specialities/skills.js +94 -0
- package/modules/specialities/specialities.js +56 -9
- package/modules/ui-design/kit/fixtures/me-cross-project-private.json +12 -0
- package/modules/ui-design/kit/fixtures/me__cross-project.json +15 -0
- package/modules/ui-design/kit/fixtures/provisioning-instance-render.json +39 -0
- package/modules/ui-design/kit/lib.js +3 -1
- package/modules/ui-design/kit/serve.js +96 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +163 -0
- package/scripts/gds/agents-sync.js +13 -3
- package/scripts/gds/autobongos-run.js +80 -2
- package/scripts/gds/autobongos-service.cmd +12 -0
- package/scripts/gds/copy-apply.js +14 -0
- package/scripts/gds/dev-box-guard.js +3 -2
- package/scripts/gds/fitness.js +9 -0
- package/scripts/gds/grade-correlation-audit.js +42 -12
- package/scripts/gds/grade-replay.js +1 -1
- package/scripts/gds/provision-core-upgrade.js +38 -7
- package/scripts/gds/provision-pin-key.js +176 -0
- package/scripts/gds/provision-render.js +62 -31
- package/scripts/gds/provision-teardown.js +7 -2
- package/scripts/gds/provision-units.js +31 -0
- package/scripts/gds/provision.js +10 -10
- package/scripts/gds/publish-manifest.js +1 -0
- package/scripts/gds/render-api.js +19 -0
- package/scripts/gds/render-payload.js +39 -65
- package/scripts/gds/run-unit-tests.js +10 -0
- package/scripts/gds/ship-finish.js +19 -20
- package/scripts/gds/smoke-dependencies.sh +37 -8
- package/scripts/gds/status.js +12 -4
- package/scripts/gds/upgrade.js +2 -2
- package/scripts/public-mirror-export.js +7 -1
- package/src/bongos/module-scope-map.js +13 -0
- package/src/bongos/serve-internal.js +3 -1
- package/src/module-api.js +10 -1
- package/src/platform-server.js +39 -0
- package/tests/account_privacy_flags.mjs +10 -6
- package/tests/agents_authoring.mjs +15 -1
- package/tests/agents_sync.mjs +106 -5
- package/tests/agents_validate.mjs +45 -0
- package/tests/api_path_404.mjs +6 -0
- package/tests/autobongos_cadence.mjs +7 -0
- package/tests/autobongos_loop.mjs +136 -1
- package/tests/autonomy_gauge.mjs +62 -0
- package/tests/blocker_hall_live_proof.mjs +275 -0
- package/tests/claim_gate_ci_unblock.mjs +116 -0
- package/tests/claim_gate_rebase.mjs +15 -3
- package/tests/collab_page.mjs +10 -0
- package/tests/conductor_main_e2e.mjs +97 -0
- package/tests/core_262_drop_box_blocked_db.mjs +143 -0
- package/tests/core_upgrade_runner.mjs +1 -0
- package/tests/dependency_writes_atomic.mjs +191 -0
- package/tests/deploy_page_projects.mjs +2 -1
- package/tests/effective_visibility_predicate.mjs +15 -0
- package/tests/est_advisory.mjs +27 -0
- package/tests/goal_map_page.mjs +26 -0
- package/tests/goal_task_relevance.mjs +518 -0
- package/tests/goal_working_area.mjs +146 -0
- package/tests/grade_attempts.mjs +61 -0
- package/tests/grade_attribution.mjs +23 -0
- package/tests/grade_correlation_audit.mjs +54 -1
- package/tests/grader_reader_lens.mjs +55 -2
- package/tests/grader_root_outage.mjs +6 -1
- package/tests/grader_subagent_tools_arg.mjs +102 -0
- package/tests/hall_approval_queue.mjs +54 -2
- package/tests/hall_settings_world.mjs +3 -1
- package/tests/hall_tweak_editor.mjs +316 -11
- package/tests/hub_account_page.mjs +394 -0
- package/tests/idea_detail_page.mjs +64 -0
- package/tests/idea_goal_ratification.mjs +64 -0
- package/tests/idea_spark_hall.mjs +60 -9
- package/tests/ideator_full_idea_shapes_space_proof.mjs +1 -1
- package/tests/landing_page.mjs +6 -3
- package/tests/module-scope-map.mjs +31 -8
- package/tests/module_api.mjs +1 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/no_phantom_mirror_workflow.mjs +24 -0
- package/tests/npm_release_preview_commands.mjs +89 -0
- package/tests/npm_release_preview_divert.mjs +164 -0
- package/tests/npm_release_preview_env.mjs +100 -0
- package/tests/npm_release_preview_proxy.mjs +162 -0
- package/tests/npm_release_preview_routes.mjs +208 -0
- package/tests/npm_release_preview_supervisor.mjs +203 -0
- package/tests/pin_write_key.mjs +383 -0
- package/tests/planning_session_skill.mjs +27 -0
- package/tests/platform_boot.mjs +71 -0
- package/tests/profile_nudge_links.mjs +86 -0
- package/tests/profile_ui_cross_project.mjs +1 -1
- package/tests/projects_hub.mjs +2 -2
- package/tests/projects_hub_app_status.mjs +249 -0
- package/tests/projects_hub_app_step.mjs +282 -52
- package/tests/projects_hub_render_connect.mjs +295 -0
- package/tests/provision_render.mjs +121 -28
- package/tests/provision_settings_apply.mjs +62 -0
- package/tests/provisioning_app_status.mjs +204 -0
- package/tests/provisioning_render_route.mjs +126 -6
- package/tests/render_check.mjs +2 -1
- package/tests/render_deploy.mjs +495 -0
- package/tests/ship_preflight.mjs +61 -8
- package/tests/smoke_dependencies_witness.mjs +46 -0
- package/tests/speciality_skills.mjs +214 -0
- package/tests/studio_room_height.mjs +70 -0
- package/tests/task_detail_includes.mjs +10 -0
- package/tests/task_visuals_instance_root.mjs +150 -0
- package/tests/tweak_batch_apply.mjs +21 -0
- package/tests/ui_design_kit.mjs +36 -4
- package/tests/upgrade.mjs +1 -1
- package/tests/upgrade_persist_pin.mjs +24 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// modules/hall-ui/public/brand-holes.js — a page's BRAND HOLES, shown as the
|
|
2
|
+
// names they stand for (task 1004381). window.OTBBrandHoles, node-require-safe.
|
|
3
|
+
//
|
|
4
|
+
// WHY. A page's source says {{productName}} where the server writes the
|
|
5
|
+
// instance's name at serve time (src/bongos/serve-internal.js applyBrandTokens),
|
|
6
|
+
// and the page readings keep the token by name (scripts/gds/page-reader.js). So
|
|
7
|
+
// the Approval queue and the tweak editor were showing an artist the raw
|
|
8
|
+
// "{{productName}}", and the editor let them type over it or lose the spaces
|
|
9
|
+
// around it (the first real round came back as "This project,{{productName}}has
|
|
10
|
+
// two environments").
|
|
11
|
+
//
|
|
12
|
+
// THE RULE. The STORED text always keeps the {{token}}: it is what copy-apply
|
|
13
|
+
// writes back into the source, and the source stays brand-free. Only the
|
|
14
|
+
// DISPLAY resolves it, from window.__BRANDING__ (the same pack the server fills
|
|
15
|
+
// from), drawn as a small fixed chip the artist reads but cannot rewrite:
|
|
16
|
+
//
|
|
17
|
+
// splitHoles(text) [{ text } | { hole: 'productName' }], in order
|
|
18
|
+
// holeNames(text) the token names, in order
|
|
19
|
+
// sameHoles(a, b) the same tokens, the same number of times
|
|
20
|
+
// holeLabel(name, brand) 'Cloud Bongos' for productName; the name itself
|
|
21
|
+
// when the pack carries no value for it
|
|
22
|
+
// holesHtml(text, opts) escaped text with each token as a chip span
|
|
23
|
+
// serializeHoles(nodes) a chip box's DOM children back to the stored text
|
|
24
|
+
(() => {
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const HOLE_RE = /\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g;
|
|
28
|
+
|
|
29
|
+
function esc(s) {
|
|
30
|
+
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function splitHoles(text) {
|
|
34
|
+
const s = String(text == null ? '' : text);
|
|
35
|
+
const out = [];
|
|
36
|
+
let at = 0;
|
|
37
|
+
for (const m of s.matchAll(HOLE_RE)) {
|
|
38
|
+
if (m.index > at) out.push({ text: s.slice(at, m.index) });
|
|
39
|
+
out.push({ hole: m[1] });
|
|
40
|
+
at = m.index + m[0].length;
|
|
41
|
+
}
|
|
42
|
+
if (at < s.length) out.push({ text: s.slice(at) });
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function holeNames(text) {
|
|
47
|
+
return splitHoles(text).filter((p) => p.hole).map((p) => p.hole);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasHoles(text) {
|
|
51
|
+
return holeNames(text).length > 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sameHoles(a, b) {
|
|
55
|
+
const x = holeNames(a).sort();
|
|
56
|
+
const y = holeNames(b).sort();
|
|
57
|
+
return x.length === y.length && x.every((n, i) => n === y[i]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The tokens serve-internal.js fills from the CLIENT pack (the origins come
|
|
61
|
+
// from the full pack only, so a chip names them instead).
|
|
62
|
+
function holeLabel(name, brand) {
|
|
63
|
+
const b = brand || (typeof window !== 'undefined' && window.__BRANDING__) || {};
|
|
64
|
+
const id = b.identity || {};
|
|
65
|
+
const co = id.company || {};
|
|
66
|
+
const cur = (b.currency && b.currency.label) || '';
|
|
67
|
+
const values = {
|
|
68
|
+
productName: id.productName,
|
|
69
|
+
worldName: id.worldName,
|
|
70
|
+
tagline: b.copy && b.copy.tagline,
|
|
71
|
+
currency: cur,
|
|
72
|
+
Currency: cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : '',
|
|
73
|
+
companyName: co.name,
|
|
74
|
+
companySite: co.site,
|
|
75
|
+
companyTagline: co.tagline,
|
|
76
|
+
};
|
|
77
|
+
const v = values[name];
|
|
78
|
+
return typeof v === 'string' && v.trim() ? v : name;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// cls: the chip's class; inBox: the chip sits in a contenteditable box, so it
|
|
82
|
+
// is marked contenteditable="false" (typed around, never into).
|
|
83
|
+
// In a box, a chip with no words on one side gets a zero-width caret stop
|
|
84
|
+
// there (CARET): a browser cannot put the caret after a non-editable chip
|
|
85
|
+
// that ends the box, so the artist could not type after it. serializeHoles
|
|
86
|
+
// drops every CARET, so it never reaches the stored text.
|
|
87
|
+
const CARET = String.fromCharCode(0x200b);
|
|
88
|
+
function holesHtml(text, { cls = 'brand-hole', brand, inBox = false } = {}) {
|
|
89
|
+
const parts = splitHoles(text);
|
|
90
|
+
return parts.map((p, i) => {
|
|
91
|
+
if (!p.hole) return esc(p.text);
|
|
92
|
+
const label = holeLabel(p.hole, brand);
|
|
93
|
+
const chipHtml = `<span class="${esc(cls)}" data-hole="${esc(p.hole)}"${inBox ? ' contenteditable="false"' : ''} title="${esc(`A fixed name the page fills in: {{${p.hole}}}`)}">${esc(label)}</span>`;
|
|
94
|
+
if (!inBox) return chipHtml;
|
|
95
|
+
const before = i === 0 ? CARET : '';
|
|
96
|
+
const after = !parts[i + 1] || parts[i + 1].hole ? CARET : '';
|
|
97
|
+
return before + chipHtml + after;
|
|
98
|
+
}).join('');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// A chip box's children back to the stored text: a text node is its words, a
|
|
102
|
+
// chip is its {{token}}, a <br> the browser inserts is a space, and any other
|
|
103
|
+
// element is its own children (a browser may wrap typed words in a <div>).
|
|
104
|
+
function serializeHoles(nodes) {
|
|
105
|
+
let out = '';
|
|
106
|
+
for (const n of Array.from(nodes || [])) {
|
|
107
|
+
if (!n) continue;
|
|
108
|
+
if (n.nodeType === 3) { out += String(n.nodeValue == null ? '' : n.nodeValue).split(CARET).join(''); continue; }
|
|
109
|
+
if (n.nodeType !== 1) continue;
|
|
110
|
+
const hole = n.getAttribute ? n.getAttribute('data-hole') : null;
|
|
111
|
+
if (hole) { out += `{{${hole}}}`; continue; }
|
|
112
|
+
if (String(n.nodeName || n.tagName || '').toUpperCase() === 'BR') { out += ' '; continue; }
|
|
113
|
+
out += serializeHoles(n.childNodes);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const api = { splitHoles, holeNames, hasHoles, sameHoles, holeLabel, holesHtml, serializeHoles };
|
|
119
|
+
if (typeof window !== 'undefined') window.OTBBrandHoles = api;
|
|
120
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
121
|
+
})();
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
|
|
149
149
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
150
150
|
|
|
151
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
151
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
152
152
|
<script src="/builders/palette.js" defer></script>
|
|
153
153
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
154
154
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
<button class="cb-ask__door" type="button" data-reply-open="${fid}">${thread.length ? 'Add a reply' : 'Reply'}</button>
|
|
170
170
|
</div>
|
|
171
171
|
<form class="cb-reply-form" id="${fid}" data-reply-form="${id}" hidden>
|
|
172
|
-
<textarea class="cb-reply-input" name="body" rows="3" maxlength="${L.MAX_REPLY}" placeholder="
|
|
172
|
+
<textarea class="cb-reply-input" name="body" rows="3" maxlength="${L.MAX_REPLY}" placeholder="Write a reply…"></textarea>
|
|
173
173
|
<div class="cb-reply-actions">
|
|
174
174
|
<button class="btn-accent" type="submit" data-reply-send="${id}">Post reply</button>
|
|
175
175
|
<button class="btn-ghost" type="button" data-reply-cancel="${fid}">Cancel</button>
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
|
|
178
178
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
179
179
|
|
|
180
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
180
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
181
181
|
<script src="/builders/palette.js" defer></script>
|
|
182
182
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
183
183
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"it's not showing anything that's been done." -->
|
|
39
39
|
<section class="scroll" id="nr-work" aria-labelledby="nr-work-h" hidden></section>
|
|
40
40
|
|
|
41
|
+
<!-- The render-deploy module's section (task 1004270): the deploy page of a project whose
|
|
42
|
+
app deploys through Render. Empty and hidden here; the module's own script fills it,
|
|
43
|
+
and only in a hall that turned the module on. -->
|
|
44
|
+
<section class="scroll" id="rd-panel" aria-labelledby="rd-h" hidden></section>
|
|
45
|
+
|
|
41
46
|
<section class="scroll" id="dp-body" aria-labelledby="dp-body-h" hidden>
|
|
42
47
|
<h2 class="scroll__h" id="dp-body-h">What users can see</h2>
|
|
43
48
|
<!-- The honest framing, and the one thing a reader must not misread:
|
|
@@ -69,7 +74,7 @@
|
|
|
69
74
|
|
|
70
75
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
71
76
|
|
|
72
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
77
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
73
78
|
<script src="/builders/palette.js" defer></script>
|
|
74
79
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
75
80
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -233,10 +233,10 @@
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
// The one fix a page can hand the owner for a repo that was not updated: sign in again
|
|
236
|
-
// with repository access, which
|
|
236
|
+
// with repository access, which lets the runner set up its own lasting write key for the
|
|
237
|
+
// repository (task 1004291, ADR 0350). Offered only
|
|
237
238
|
// for the two causes it can cure — a sign-in does nothing for GitHub being ahead of the
|
|
238
|
-
// box,
|
|
239
|
-
// GITHUB_TOKEN_TTL_MS) rather than promising the repo will simply catch up.
|
|
239
|
+
// box, for a project with no repo, or for one that needs its own account first.
|
|
240
240
|
const RECONNECT_CURES = new Set(['no_access', 'refused']);
|
|
241
241
|
function reconnectHtml(p, code) {
|
|
242
242
|
if (!RECONNECT_CURES.has(code)) return '';
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
const href = `${API}/auth/web/start?repos=1&return=${encodeURIComponent(back)}`;
|
|
250
250
|
return `
|
|
251
251
|
<p class="ov-row__sub">If you own this project, <a href="${escapeHtml(href)}">reconnect GitHub with repository access</a>.
|
|
252
|
-
|
|
252
|
+
Reconnecting sets up <strong>lasting</strong> access: the hub keeps a key of its own for this repository, so this change and every later upgrade are saved to GitHub without asking you again.</p>`;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
function stepOneHtml(p) {
|
|
@@ -592,8 +592,17 @@
|
|
|
592
592
|
return rows.find((i) => i && String(i.slug) === slug) || null;
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
// A Render project's hall has no provisioning door (task 1004270): its /deploy page is the
|
|
596
|
+
// render-deploy module's, so the door's own reading is skipped rather than failing on routes
|
|
597
|
+
// that hall does not have.
|
|
598
|
+
function doorIsHere() {
|
|
599
|
+
const mods = window.__MODULES__;
|
|
600
|
+
return !(mods && mods.enabled && mods.enabled.provisioning === false);
|
|
601
|
+
}
|
|
602
|
+
|
|
595
603
|
async function load() {
|
|
596
604
|
const slug = scopeSlug();
|
|
605
|
+
if (!slug && !doorIsHere()) { $('dp-sub').textContent = 'Render'; return; }
|
|
597
606
|
try {
|
|
598
607
|
if (!slug) {
|
|
599
608
|
// THE HALL'S OWN PROJECT. On the control plane that is the self row, which is why
|
|
@@ -650,10 +659,15 @@
|
|
|
650
659
|
// module is on, so a hall without it never asks for a script that would 404.
|
|
651
660
|
function loadModuleSections() {
|
|
652
661
|
const mods = window.__MODULES__;
|
|
653
|
-
if (scopeSlug() || !(mods && mods.enabled
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
662
|
+
if (scopeSlug() || !(mods && mods.enabled)) return;
|
|
663
|
+
// Each module lends the page its own script, and only where it is on: a hall without a
|
|
664
|
+
// module never asks for a script that would 404.
|
|
665
|
+
for (const [key, src] of [['npm-release', '/modules/npm-release/public/work.js'], ['render-deploy', '/modules/render-deploy/public/deploy.js']]) {
|
|
666
|
+
if (!mods.enabled[key]) continue;
|
|
667
|
+
const s = document.createElement('script');
|
|
668
|
+
s.src = src;
|
|
669
|
+
document.body.appendChild(s);
|
|
670
|
+
}
|
|
657
671
|
}
|
|
658
672
|
|
|
659
673
|
document.addEventListener('DOMContentLoaded', () => { load(); loadModuleSections(); });
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
99
99
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
100
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
100
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
101
101
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
102
102
|
<script src="/builders/palette.js" defer></script>
|
|
103
103
|
<script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
|
|
@@ -441,6 +441,25 @@
|
|
|
441
441
|
+ `${escapeHtml(label)}${inherited ? '<i class="gr-cat__from" aria-hidden="true">via goal</i>' : ''}</span>`;
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// The hub's origin, for the links a hall sends to the person's account page
|
|
445
|
+
// (task 1004259 / 1003485, ADR 0337 D4.3). Stamped server-side into
|
|
446
|
+
// <meta name="hub-origin" content="{{hubOrigin}}"> — the hub itself, or the
|
|
447
|
+
// hub a federated project signs in through — and empty on a vanilla instance.
|
|
448
|
+
// A bare https origin only: an empty or pathy value must never become a
|
|
449
|
+
// same-host link claiming to be the hub (the profile-canonical.js rule).
|
|
450
|
+
// null means "no hub", and every caller then renders no link.
|
|
451
|
+
function hubOrigin() {
|
|
452
|
+
const meta = document.querySelector('meta[name="hub-origin"]');
|
|
453
|
+
const raw = ((meta && meta.getAttribute('content')) || '').trim().replace(/\/+$/, '');
|
|
454
|
+
if (!raw) return null;
|
|
455
|
+
try {
|
|
456
|
+
const url = new URL(raw);
|
|
457
|
+
if (url.protocol !== 'https:' || url.username || url.password) return null;
|
|
458
|
+
if (url.pathname !== '/' || url.search || url.hash) return null;
|
|
459
|
+
return url.origin;
|
|
460
|
+
} catch (err) { return null; }
|
|
461
|
+
}
|
|
462
|
+
|
|
444
463
|
window.OTB = {
|
|
445
464
|
escapeHtml, $, fmtNum, num, fmtInt, fmtCompact, fmtDate, rankLabel, toast, moduleOn, disciplines,
|
|
446
465
|
// task 1003936 — ONE word for the role axis, all six surfaces
|
|
@@ -451,5 +470,6 @@
|
|
|
451
470
|
contributeHallWidget, hallWidgets, hallWidgetIds,
|
|
452
471
|
splitCriterion,
|
|
453
472
|
categoryChip, // task 1003276 — shared by the Goals page, the Tasks board and the task page
|
|
473
|
+
hubOrigin, // task 1003485 — the hub account page's origin, for Settings and the profile nudge
|
|
454
474
|
};
|
|
455
475
|
})();
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
100
100
|
|
|
101
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
101
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
102
102
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
103
103
|
<script src="/builders/palette.js" defer></script>
|
|
104
104
|
<script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
57
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
58
58
|
<script src="/builders/palette.js" defer></script>
|
|
59
59
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
60
60
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<div id="gate-modal-root"></div>
|
|
77
77
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
78
78
|
|
|
79
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
79
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
80
80
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
81
81
|
<script src="/builders/palette.js" defer></script>
|
|
82
82
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
|
|
236
236
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
237
237
|
|
|
238
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
238
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
239
239
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
240
240
|
<script src="/builders/palette.js" defer></script>
|
|
241
241
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
136
136
|
|
|
137
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
137
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
138
138
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
139
139
|
<script src="/builders/palette.js" defer></script>
|
|
140
140
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
54
54
|
|
|
55
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
55
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
56
56
|
<script src="/builders/palette.js" defer></script>
|
|
57
57
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
|
|
58
58
|
<!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
|
|
@@ -141,6 +141,33 @@
|
|
|
141
141
|
}).join('')}</div>`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// The author's scope recommendation (task 1004134; the field is BV2.R13).
|
|
145
|
+
// What the author thinks their Full Idea turned out to be, a goal of its own
|
|
146
|
+
// or a criterion inside an existing goal: the line written for the board, who
|
|
147
|
+
// read this page while deciding. The question and the option labels are the
|
|
148
|
+
// SERVER's (the template's `recommendation` spec), never re-typed here. Three
|
|
149
|
+
// honest outcomes: a served option is shown by its label; a value the served
|
|
150
|
+
// options do not name is printed as stored rather than dropped; NULL (every
|
|
151
|
+
// Full Idea filed before the question existed, and any terse CLI filing) says
|
|
152
|
+
// the author did not say, and invents nothing. It is the author's reading and
|
|
153
|
+
// never the board's decision, so the line says whose it is. The two sentences
|
|
154
|
+
// are the Board Room's own (board-room.js recommendationHtml), so one field
|
|
155
|
+
// reads in one vocabulary on every page that shows it (docs/voice.md V1).
|
|
156
|
+
function recommendationRow(idea, template) {
|
|
157
|
+
const spec = template && template.recommendation;
|
|
158
|
+
const q = spec && spec.label ? spec.label : 'Recommended scope';
|
|
159
|
+
const v = idea.recommendation;
|
|
160
|
+
let a;
|
|
161
|
+
if (v === null || v === undefined || v === '') {
|
|
162
|
+
a = '<p class="ig-answer__a" data-empty="1">Its author gave no scope recommendation.</p>';
|
|
163
|
+
} else {
|
|
164
|
+
const opt = spec && Array.isArray(spec.options) ? spec.options.find((o) => o && o.value === v) : null;
|
|
165
|
+
const label = opt && opt.label ? opt.label.charAt(0).toLowerCase() + opt.label.slice(1) : String(v);
|
|
166
|
+
a = `<p class="ig-answer__a">Its author recommends this becomes <strong>${escapeHtml(label)}</strong>. The board decides.</p>`;
|
|
167
|
+
}
|
|
168
|
+
return `<div class="ig-answers"><div class="ig-answer"><p class="ig-answer__q">${escapeHtml(q)}</p>${a}</div></div>`;
|
|
169
|
+
}
|
|
170
|
+
|
|
144
171
|
// ---- the R154 mark control -------------------------------------------------
|
|
145
172
|
|
|
146
173
|
// Rendered only when the viewer IS the author. The server re-checks; this is
|
|
@@ -433,6 +460,7 @@
|
|
|
433
460
|
`<div class="ig-answer"><p class="ig-answer__q">${escapeHtml(k)}</p><p class="ig-answer__a">${escapeHtml(String(idea.field_values[k] || ''))}</p></div>`
|
|
434
461
|
).join('')}</div>`);
|
|
435
462
|
}
|
|
463
|
+
parts.push(recommendationRow(idea, template));
|
|
436
464
|
// A Full Idea's body_md is the original quick-idea text it grew out of, when
|
|
437
465
|
// it had one. Shown after the answers, subordinate to them.
|
|
438
466
|
if (String(idea.body_md || '').trim()) {
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
|
|
226
226
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
227
227
|
|
|
228
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
228
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
229
229
|
<script src="/builders/palette.js" defer></script>
|
|
230
230
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
231
231
|
<!-- task 1003069 (R08) — the quick-filing form's routed result links a task
|
|
@@ -86,6 +86,14 @@
|
|
|
86
86
|
let allOpenGoals = []; // the picker's "browse everything" half
|
|
87
87
|
let allGoalsById = new Map(); // for naming the goal a routed capture landed in
|
|
88
88
|
let goalTouchedByUser = false; // once the builder picks, stop clobbering it with fresh suggestions
|
|
89
|
+
// The suggester's out-of-order guard — the exact sibling of `gravitySeq`
|
|
90
|
+
// below (task 1004187). Both reads are fired from the same debounce, so both
|
|
91
|
+
// invite the same race: a slow answer for an earlier title landing after a
|
|
92
|
+
// fast one for the current title would repaint the picker with options for
|
|
93
|
+
// words the builder has already replaced. `goalTouchedByUser` answers a
|
|
94
|
+
// different question — has the builder picked manually? — and says nothing
|
|
95
|
+
// about ordering.
|
|
96
|
+
let goalSuggestSeq = 0;
|
|
89
97
|
let quickSuggestTimer = null;
|
|
90
98
|
let quickFiling = false;
|
|
91
99
|
// GRAVITY (task 1004083 / spec R02). The landing verdict for the draft as it
|
|
@@ -584,10 +592,12 @@
|
|
|
584
592
|
// degrades to "no suggestions"; the homeless option and the full goal list
|
|
585
593
|
// are still there either way (goal-suggest.js's own fail-open contract).
|
|
586
594
|
async function refreshGoalSuggestions() {
|
|
595
|
+
const seq = ++goalSuggestSeq;
|
|
587
596
|
if (goalTouchedByUser) return; // never yank a manual pick out from under someone
|
|
588
597
|
const title = fqTitleEl().value.trim();
|
|
589
598
|
if (!title || !buildingVersionIds.length) { renderGoalOptions([]); return; }
|
|
590
599
|
const kind = fqKindEl().value;
|
|
600
|
+
let merged = [];
|
|
591
601
|
try {
|
|
592
602
|
const results = await Promise.all(buildingVersionIds.map((versionId) => api.goals.getGoalsSuggestions({
|
|
593
603
|
query: { version: versionId, title, kind, limit: 5 },
|
|
@@ -600,11 +610,17 @@
|
|
|
600
610
|
if (!prev || g.score > prev.score) byId.set(String(g.id), g);
|
|
601
611
|
}
|
|
602
612
|
}
|
|
603
|
-
|
|
604
|
-
renderGoalOptions(merged);
|
|
613
|
+
merged = Array.from(byId.values()).sort((a, b) => b.score - a.score).slice(0, 5);
|
|
605
614
|
} catch (_) {
|
|
606
|
-
|
|
615
|
+
merged = [];
|
|
607
616
|
}
|
|
617
|
+
// A stale answer never paints. See goalSuggestSeq above. BOTH async exits
|
|
618
|
+
// share the one guard, exactly as refreshGravity's do: a read that fails
|
|
619
|
+
// late would otherwise clear a newer read's options just as surely as a
|
|
620
|
+
// read that succeeds late would replace them. The no-title return above is
|
|
621
|
+
// synchronous, so nothing can have superseded it.
|
|
622
|
+
if (seq !== goalSuggestSeq) return;
|
|
623
|
+
renderGoalOptions(merged);
|
|
608
624
|
}
|
|
609
625
|
|
|
610
626
|
// ---------------------------------------------------------------------------
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
that on the #hall-h1 marker below, so the marker must stay index-only. -->
|
|
12
12
|
<title>Builders' Hall</title>
|
|
13
13
|
<meta name="description" content="The hall of the builders who shape this world. Standing, achievements, and work waiting to be claimed.">
|
|
14
|
+
<!-- the hub's origin, stamped server-side (task 1003485): the profile nudge
|
|
15
|
+
sends "add a short bio" to the hub's account page, where the bio is edited.
|
|
16
|
+
Empty on an instance with no hub, and the bio half is then not asked for. -->
|
|
17
|
+
<meta name="hub-origin" content="{{hubOrigin}}">
|
|
14
18
|
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
15
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
16
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -102,7 +106,7 @@
|
|
|
102
106
|
<script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
|
|
103
107
|
<!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
|
|
104
108
|
BEFORE builders.js so the chrome ids exist when events are wired. -->
|
|
105
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
109
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy" defer></script>
|
|
106
110
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
107
111
|
<script src="/builders/palette.js" defer></script>
|
|
108
112
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
82
82
|
|
|
83
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
83
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
84
84
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
85
85
|
<script src="/builders/palette.js" defer></script>
|
|
86
86
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -54,18 +54,35 @@
|
|
|
54
54
|
}
|
|
55
55
|
// Name what is actually missing: "add a bio" is actionable, "complete your
|
|
56
56
|
// profile" is a chore of unknown size.
|
|
57
|
+
//
|
|
58
|
+
// Each half links to where it is actually EDITED (task 1003485): the bio is
|
|
59
|
+
// a platform-account field, edited on the hub's account page (task 1004259,
|
|
60
|
+
// ADR 0337 D4.3); the kinds of work are this project's own, in Settings. The
|
|
61
|
+
// line used to send both to /settings, which has no bio field — the "goes to
|
|
62
|
+
// an empty spot" bug. A half with nowhere to go (no hub origin) is not asked
|
|
63
|
+
// for at all, rather than pointed at a page that cannot answer it.
|
|
57
64
|
const missing = Array.isArray(state.missing) ? state.missing : [];
|
|
65
|
+
const hub = window.OTB.hubOrigin();
|
|
58
66
|
const parts = [];
|
|
59
|
-
if (missing.includes('bio')) parts.push('a short bio');
|
|
60
|
-
if (missing.includes('disciplines')) parts.push('the kinds of work you want');
|
|
61
|
-
|
|
67
|
+
if (missing.includes('bio') && hub) parts.push({ href: `${hub}/account`, text: 'a short bio' });
|
|
68
|
+
if (missing.includes('disciplines')) parts.push({ href: '/settings#disciplines', text: 'the kinds of work you want' });
|
|
69
|
+
if (!parts.length) {
|
|
70
|
+
box.innerHTML = '';
|
|
71
|
+
window.OTBAttention?.refresh();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const link = (part, lead) => `<a class="ginbox__goal" href="${escapeHtml(part.href)}">${escapeHtml(lead + part.text)}</a>`;
|
|
75
|
+
// One flex item, so the two links read as one sentence rather than stacking.
|
|
76
|
+
const what = parts.length === 2
|
|
77
|
+
? `<span>${link(parts[0], 'Add ')} and ${link(parts[1], '')}</span>`
|
|
78
|
+
: link(parts[0], 'Add ');
|
|
62
79
|
box.innerHTML = '<h3 class="ginbox__sec-h">Complete your profile</h3>'
|
|
63
80
|
+ '<ul class="ginbox">'
|
|
64
81
|
+ '<li class="ginbox__item">'
|
|
65
82
|
// .ginbox__body / .ginbox__actions are the classes style.css actually
|
|
66
83
|
// defines (the body is the flex column that stacks label over sub-line).
|
|
67
84
|
+ '<div class="ginbox__body">'
|
|
68
|
-
+
|
|
85
|
+
+ what
|
|
69
86
|
+ '<span class="ginbox__sub">Shown on your public profile — it helps others know what you work on.</span>'
|
|
70
87
|
+ '</div>'
|
|
71
88
|
+ '<div class="ginbox__actions">'
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
|
|
188
188
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
189
189
|
chrome exists before the page script runs. -->
|
|
190
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
190
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
191
191
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
192
192
|
<script src="/builders/palette.js" defer></script>
|
|
193
193
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
// ids may serialize as strings (pg bigint) — compare canonicalized.
|
|
288
288
|
const isSelf = selfId != null && data.builder && String(data.builder.id) === String(selfId);
|
|
289
289
|
const manage = isSelf
|
|
290
|
-
? `<p class="scroll__lede">This is you — <a href="/settings#cross-project">manage its visibility in Settings →
|
|
290
|
+
? `<p class="scroll__lede">This is you — <a href="/settings#cross-project">manage its visibility in Settings → Profile and privacy</a>.</p>`
|
|
291
291
|
: '';
|
|
292
292
|
const cp = data.cross_project;
|
|
293
293
|
if (!cp) {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
70
70
|
chrome exists before the page script runs. -->
|
|
71
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
71
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
72
72
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
73
73
|
<script src="/builders/palette.js" defer></script>
|
|
74
74
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|