@bongos/core 1.19.1080 → 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 +573 -188
- 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 +34 -0
- package/clients/bongos-client/index.cjs +34 -0
- package/clients/bongos-client/index.d.ts +53 -4
- package/clients/bongos-client/index.mjs +34 -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/0343-a-module-score-is-a-security-gate-then-an-average-of-visible-parts.md +2 -0
- package/docs/adr/0347-every-store-module-ships-a-how-to.md +135 -0
- 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 +4 -0
- package/docs/api/openapi.json +995 -40
- package/docs/api-reference.md +33 -8
- 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 +8 -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/migrations/provisioning_028_render_standup.sql +46 -0
- package/modules/provisioning/module.json +4 -2
- package/modules/provisioning/pollers/app-liveness.js +110 -0
- package/modules/provisioning/provisioning.js +5 -5
- package/modules/provisioning/render-lookup.js +68 -0
- package/modules/provisioning/render-standup.js +134 -0
- package/modules/provisioning/routes/render-standup.js +165 -0
- 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 +173 -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 +189 -0
- package/scripts/gds/provision-teardown.js +9 -2
- package/scripts/gds/provision-units.js +31 -0
- package/scripts/gds/provision.js +12 -12
- package/scripts/gds/publish-manifest.js +1 -0
- package/scripts/gds/render-api.js +197 -0
- package/scripts/gds/render-payload.js +84 -0
- 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 +362 -0
- package/tests/provision_settings_apply.mjs +62 -0
- package/tests/provisioning_app_status.mjs +204 -0
- package/tests/provisioning_render_route.mjs +336 -0
- package/tests/provisioning_settings_apply.mjs +1 -1
- package/tests/provisioning_teardown_intent.mjs +2 -2
- package/tests/render_api.mjs +153 -0
- 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
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Settings — {{worldName}}</title>
|
|
7
7
|
<meta name="description" content="Your preferences — connections, access keys, skill voices, and sounds.">
|
|
8
|
+
<!-- the hub's origin, stamped server-side (task 1004259): the Profile and
|
|
9
|
+
privacy row links to its account page. Empty on an instance with no hub. -->
|
|
10
|
+
<meta name="hub-origin" content="{{hubOrigin}}">
|
|
8
11
|
<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>">
|
|
9
12
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
13
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -88,37 +91,23 @@
|
|
|
88
91
|
<p class="section-note">Saves when you press Save; shows everywhere at once.</p>
|
|
89
92
|
</section>
|
|
90
93
|
|
|
91
|
-
<!--
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
<!-- PROFILE AND PRIVACY — one link out to the hub's account page
|
|
95
|
+
(task 1004259, ADR 0337 D4.3). Handle, bio, links, public/private,
|
|
96
|
+
recruiters and the hide-my-numbers switch belong to the platform
|
|
97
|
+
account, so they are edited once on the hub; this hall carried the
|
|
98
|
+
privacy half as two sections ("Cross-project record", "Profile
|
|
99
|
+
privacy") and now only points there. settings.js sets the href from
|
|
100
|
+
the hub-origin meta and reveals the row; with no hub (a vanilla
|
|
101
|
+
instance) it stays hidden. -->
|
|
102
|
+
<section class="scroll" id="account-hub-scroll" aria-labelledby="account-hub-h" data-group="account" hidden>
|
|
103
|
+
<h2 id="account-hub-h" class="scroll__h">Profile and privacy</h2>
|
|
96
104
|
<p class="scroll__lede">
|
|
97
|
-
Your
|
|
98
|
-
|
|
99
|
-
your
|
|
105
|
+
Your handle, bio and links, whether your profile page is public,
|
|
106
|
+
whether recruiters can find you, and whether your record shows are
|
|
107
|
+
part of your account across every project — so you change them in
|
|
108
|
+
one place, on your account page.
|
|
100
109
|
</p>
|
|
101
|
-
<div id="
|
|
102
|
-
<p class="settings-status" id="cross-project-status" role="status" aria-live="polite"></p>
|
|
103
|
-
</section>
|
|
104
|
-
|
|
105
|
-
<!-- Profile privacy (platform-identity module, privacy spec D1 / task
|
|
106
|
-
1002966). The same own-scoped GET/PATCH /me/cross-project as the
|
|
107
|
-
section above, so it reveals and hides with it; P04 later moves
|
|
108
|
-
both into the Profile rail group. -->
|
|
109
|
-
<section class="scroll" id="privacy-scroll" aria-labelledby="privacy-h" data-group="account" hidden>
|
|
110
|
-
<h2 id="privacy-h" class="scroll__h">Profile privacy</h2>
|
|
111
|
-
<p class="scroll__lede">
|
|
112
|
-
Who can find you. Your profile page can be public or private, and
|
|
113
|
-
recruiters looking for builders are allowed in separately — so a
|
|
114
|
-
private profile can still be reachable for work, and a public one
|
|
115
|
-
can stay out of recruiting entirely.
|
|
116
|
-
</p>
|
|
117
|
-
<div id="privacy-rows"></div>
|
|
118
|
-
<p class="settings-status" id="privacy-status" role="status" aria-live="polite"></p>
|
|
119
|
-
<p class="section-note">Your profile page setting applies now — private already answers "not found"
|
|
120
|
-
everywhere your profile is read. Recruiter discoverability applies now too — switching
|
|
121
|
-
it off removes you from both recruiting surfaces immediately, and back on puts you there again.</p>
|
|
110
|
+
<div class="row-end"><a class="pbtn" id="account-hub-link" href="/account">Open your account page</a></div>
|
|
122
111
|
</section>
|
|
123
112
|
|
|
124
113
|
<!-- CRAFT — discipline preferences (GET /me, PATCH /me/disciplines). -->
|
|
@@ -407,7 +396,7 @@
|
|
|
407
396
|
|
|
408
397
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
409
398
|
chrome exists before the page scripts run. -->
|
|
410
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
399
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
411
400
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
412
401
|
<script src="/builders/palette.js" defer></script>
|
|
413
402
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -485,179 +485,23 @@
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
// ---- section:
|
|
488
|
+
// ---- section: PROFILE AND PRIVACY — a link out to the hub (task 1004259)
|
|
489
489
|
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
? `Your record: ${totals.projects} project${totals.projects === 1 ? '' : 's'} · ` +
|
|
506
|
-
`${totals.credits} credits · ${totals.works_shipped} works shipped · ${totals.karma} karma.`
|
|
507
|
-
: 'No cross-project activity reported yet.';
|
|
508
|
-
const consentNote = data && data.profile_state !== 'active'
|
|
509
|
-
? ' Your profile publishes after your next sign-in at the hub.'
|
|
510
|
-
: '';
|
|
511
|
-
wrap.innerHTML =
|
|
512
|
-
'<div class="sound-row" data-cross-project="hide">' +
|
|
513
|
-
'<div class="sound-row__text">' +
|
|
514
|
-
'<div class="sound-row__label">Show my cross-project record</div>' +
|
|
515
|
-
`<div class="sound-row__desc">${escapeHtml(totalsLine + consentNote)} You always see your own numbers here.</div>` +
|
|
516
|
-
'</div>' +
|
|
517
|
-
switchHtml(on, 'Show my cross-project record') +
|
|
518
|
-
'</div>';
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// ---- section: PROFILE PRIVACY (account_visibility + recruiter_discoverable
|
|
522
|
-
// — privacy spec D1/D3, task 1002966)
|
|
523
|
-
//
|
|
524
|
-
// Two more rows on the SAME own-scoped GET/PATCH /me/cross-project, because
|
|
525
|
-
// both flags live on the hub account beside hide_stats (never behavior_prefs).
|
|
526
|
-
// The recruiter row's copy must name EXACTLY what being discoverable grants —
|
|
527
|
-
// the spec's D3 sliver — and that grant shrinks to name-only when the
|
|
528
|
-
// cross-project record is hidden, because the rest of the sliver is derived
|
|
529
|
-
// from it. `recruiter_discoverable` is tri-state server-side: null means the
|
|
530
|
-
// answer follows the visibility setting, which the row says out loud. The
|
|
531
|
-
// toggle is deliberately BINARY (spec D1: "one column, one settings toggle");
|
|
532
|
-
// resetting back to null stays an API affordance, not a third UI state.
|
|
533
|
-
//
|
|
534
|
-
// COPY RULE — a toggle may promise exactly what is enforced, and BOTH rows now
|
|
535
|
-
// are, one task apart. VISIBILITY went live with task 1002967: every public
|
|
536
|
-
// read joins the shared effective-visibility predicate, so private really does
|
|
537
|
-
// 404 — including to the owner, which the row must say out loud: a page that
|
|
538
|
-
// vanishes for you too is surprising unless you are told where your record went
|
|
539
|
-
// (here, Settings). DISCOVERABILITY went live in two halves: task 1002290 (the People directory)
|
|
540
|
-
// and task 1002968 (the D3 pre-accept sliver). BOTH recruiting reads now join
|
|
541
|
-
// the reach axis — listScoutingRollup via scoutingListableSql, the sliver via
|
|
542
|
-
// recruiterSliverVisibleSql — so the row may finally name both, which is exactly
|
|
543
|
-
// what 1002290 narrowed it away from while the second surface was unbuilt. The
|
|
544
|
-
// wider claim is CHECKABLE rather than open-ended: account_privacy_flags.mjs
|
|
545
|
-
// pins that the recruiting surface has exactly those two reads and that each one
|
|
546
|
-
// joins reach, so a third would red the guard before this sentence could quietly
|
|
547
|
-
// become false.
|
|
548
|
-
function renderPrivacy(data) {
|
|
549
|
-
const wrap = $('#privacy-rows');
|
|
550
|
-
if (!wrap) return;
|
|
551
|
-
const isPublic = !data || data.account_visibility !== 'private';
|
|
552
|
-
const discoverable = !!(data && data.recruiter_discoverable_effective);
|
|
553
|
-
const follows = !data || data.recruiter_discoverable == null;
|
|
554
|
-
const grant = data && data.hide_stats
|
|
555
|
-
? 'Because your cross-project record is hidden, they will see your name and your picture, and nothing else.'
|
|
556
|
-
: 'They will see your name, your picture, your roles, and whether you have been active in the last 30 days — never your credits, works shipped, karma, or the projects you build on.';
|
|
557
|
-
// The surprising half, and only where it applies: a private profile answers
|
|
558
|
-
// "not found" to every visitor, and this one switch is the exception.
|
|
559
|
-
const despitePrivate = isPublic ? ''
|
|
560
|
-
: ' This is the one thing a recruiter can see while your profile page is private.';
|
|
561
|
-
wrap.innerHTML =
|
|
562
|
-
privacyRow('visibility', 'Public profile page', isPublic ? 'Public' : 'Private', isPublic,
|
|
563
|
-
'Public means anyone can open your profile page and find you by name. Private hides the page from everyone — including you: it answers "not found" to every visitor, and you read your own record here in Settings instead.', '') +
|
|
564
|
-
privacyRow('discoverable', 'Discoverable by recruiters', discoverable ? 'On' : 'Off', discoverable,
|
|
565
|
-
`Recruiters looking for builders can find you. ${grant}${despitePrivate} Turning this off removes you from both recruiting surfaces — the recruiting directory, and a recruiter's look-up of one builder — straight away.`,
|
|
566
|
-
follows ? ' <span class="voices__default">· follows your profile setting</span>' : '');
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// One row of the privacy section, in the same sound-row markup every toggle on
|
|
570
|
-
// this page uses. `note` is trusted markup (a caller-supplied span), the label
|
|
571
|
-
// and description are escaped.
|
|
572
|
-
function privacyRow(key, label, state, on, desc, note) {
|
|
573
|
-
return `<div class="sound-row" data-privacy="${key}">` +
|
|
574
|
-
'<div class="sound-row__text">' +
|
|
575
|
-
`<div class="sound-row__label">${escapeHtml(label)}<span class="prow__tag">${escapeHtml(state)}</span>${note}</div>` +
|
|
576
|
-
`<div class="sound-row__desc">${escapeHtml(desc)}</div>` +
|
|
577
|
-
'</div>' +
|
|
578
|
-
switchHtml(on, `${label}: ${state}`) +
|
|
579
|
-
'</div>';
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// The status/toast line for a privacy toggle. Deliberately says only what the
|
|
583
|
-
// flag itself changed — the exact grant lives in the row's description, which
|
|
584
|
-
// re-renders from the server right after the save.
|
|
585
|
-
function privacyMsg(key, on) {
|
|
586
|
-
if (key === 'visibility') {
|
|
587
|
-
return on
|
|
588
|
-
? 'Saved — your profile page is set to public.'
|
|
589
|
-
: 'Saved — your profile page is set to private.';
|
|
590
|
-
}
|
|
591
|
-
return on
|
|
592
|
-
? 'Saved — recruiters can find you.'
|
|
593
|
-
: 'Saved — you are out of the recruiting surfaces.';
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// PATCH any own-scoped privacy flag (routes/profile.js): { hide_stats } and/or
|
|
597
|
-
// { account_visibility, recruiter_discoverable }. Always re-reads afterwards —
|
|
598
|
-
// the effective recruiter answer is derived server-side, so the server's own
|
|
599
|
-
// view is the only state worth rendering.
|
|
600
|
-
async function savePrivacy(body, statusSel, msg, btn) {
|
|
601
|
-
const status = $(statusSel);
|
|
602
|
-
if (status) status.textContent = 'Saving…';
|
|
603
|
-
if (btn) btn.disabled = true;
|
|
604
|
-
try {
|
|
605
|
-
const res = await api.request('PATCH', `${API}/me/cross-project`, { body });
|
|
606
|
-
if (res.status === 401) { toAuth(); return; }
|
|
607
|
-
if (!res.ok) throw new Error(`cross-project PATCH → ${res.status}`);
|
|
608
|
-
await loadCrossProject(); // re-render from the server's truth (flags + record)
|
|
609
|
-
if (status) status.textContent = msg;
|
|
610
|
-
toast(msg);
|
|
611
|
-
} catch (err) {
|
|
612
|
-
console.error('[settings] save privacy failed', err);
|
|
613
|
-
if (status) status.textContent = 'Could not save. Try again.';
|
|
614
|
-
toast('Could not save your privacy preference. Try again.');
|
|
615
|
-
if (btn) btn.disabled = false;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// The hide flag. Body: { hide_stats: <bool> } (routes/profile.js).
|
|
620
|
-
function saveCrossProject(hide, btn) {
|
|
621
|
-
return savePrivacy({ hide_stats: hide }, '#cross-project-status', hide
|
|
622
|
-
? 'Cross-project record hidden — removed from your profile and directories immediately.'
|
|
623
|
-
: 'Cross-project record shown on your public profile.', btn);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
let crossProjectWired = false;
|
|
627
|
-
async function loadCrossProject() {
|
|
628
|
-
const section = $('#cross-project-scroll');
|
|
629
|
-
if (!section) return;
|
|
630
|
-
try {
|
|
631
|
-
const res = await api.request('GET', `${API}/me/cross-project`);
|
|
632
|
-
if (res.status === 401) { toAuth(); return; }
|
|
633
|
-
if (!res.ok) return; // module off / route absent → the section stays hidden
|
|
634
|
-
renderCrossProject(res.data);
|
|
635
|
-
renderPrivacy(res.data);
|
|
636
|
-
section.hidden = false;
|
|
637
|
-
const privacy = $('#privacy-scroll');
|
|
638
|
-
if (privacy) privacy.hidden = false;
|
|
639
|
-
if (!crossProjectWired) {
|
|
640
|
-
crossProjectWired = true;
|
|
641
|
-
$('#cross-project-rows').addEventListener('click', (e) => {
|
|
642
|
-
const b = e.target.closest('.sound-toggle');
|
|
643
|
-
if (!b || b.disabled) return;
|
|
644
|
-
saveCrossProject(b.dataset.on === '1', b); // shown → hide; hidden → show
|
|
645
|
-
});
|
|
646
|
-
$('#privacy-rows').addEventListener('click', (e) => {
|
|
647
|
-
const b = e.target.closest('.sound-toggle');
|
|
648
|
-
if (!b || b.disabled) return;
|
|
649
|
-
const row = b.closest('[data-privacy]');
|
|
650
|
-
const on = b.dataset.on === '1';
|
|
651
|
-
const body = row && row.dataset.privacy === 'visibility'
|
|
652
|
-
? { account_visibility: on ? 'private' : 'public' }
|
|
653
|
-
: { recruiter_discoverable: !on };
|
|
654
|
-
savePrivacy(body, '#privacy-status', privacyMsg(row && row.dataset.privacy, !on), b);
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
} catch (err) {
|
|
658
|
-
// Network/parse failure on an optional module section — stay hidden, log only.
|
|
659
|
-
console.error('[settings] failed to load cross-project', err);
|
|
660
|
-
}
|
|
490
|
+
// Handle, bio, links, public/private, recruiters and the hide-my-numbers switch
|
|
491
|
+
// belong to the PLATFORM account, not to this project (ADR 0337 D4.3), so they
|
|
492
|
+
// are edited once, on the hub's account page — this hall used to carry the
|
|
493
|
+
// privacy half as its own two sections, and now carries one row that points
|
|
494
|
+
// there (the /my-projects precedent: a user-level page that lives on the hub
|
|
495
|
+
// ships no copy in the hall). The hub's origin comes from OTB.hubOrigin()
|
|
496
|
+
// (dom-utils.js): a bare https origin, or null on a vanilla instance, which
|
|
497
|
+
// has no hub and so no row.
|
|
498
|
+
function loadAccountHub() {
|
|
499
|
+
const section = $('#account-hub-scroll');
|
|
500
|
+
const link = $('#account-hub-link');
|
|
501
|
+
const origin = window.OTB.hubOrigin();
|
|
502
|
+
if (!section || !link || !origin) return; // no hub → the row stays hidden
|
|
503
|
+
link.href = `${origin}/account`;
|
|
504
|
+
section.hidden = false;
|
|
661
505
|
}
|
|
662
506
|
|
|
663
507
|
// ---- section 2: CRAFT (discipline preferences) ---------------------------
|
|
@@ -1215,8 +1059,11 @@
|
|
|
1215
1059
|
const PANEL_ANCHORS = {
|
|
1216
1060
|
'connections': 'connections-scroll',
|
|
1217
1061
|
'display-name': 'display-name-scroll',
|
|
1218
|
-
|
|
1219
|
-
|
|
1062
|
+
// task 1004259: the two privacy panels became one link-out row; their old
|
|
1063
|
+
// anchors still land on it (the hall profile links /settings#cross-project).
|
|
1064
|
+
'profile-privacy': 'account-hub-scroll',
|
|
1065
|
+
'cross-project': 'account-hub-scroll',
|
|
1066
|
+
'privacy': 'account-hub-scroll',
|
|
1220
1067
|
'disciplines': 'craft-scroll',
|
|
1221
1068
|
'cli': 'cli-scroll',
|
|
1222
1069
|
'art-key': 'art-key',
|
|
@@ -1363,9 +1210,9 @@
|
|
|
1363
1210
|
// (Rendering — the card-richness ladder — is settings-render.js, on the same
|
|
1364
1211
|
// precedent: it became a four-rung picker in task 1002518 and this file is
|
|
1365
1212
|
// against the 1500-line ratchet.)
|
|
1366
|
-
//
|
|
1367
|
-
//
|
|
1368
|
-
|
|
1213
|
+
// Profile and privacy — one row linking out to the hub's account page
|
|
1214
|
+
// (task 1004259); hidden where the page carries no hub origin.
|
|
1215
|
+
loadAccountHub();
|
|
1369
1216
|
// Image key (per-builder Gemini key, task 1013) — own try/catch; `me` carries
|
|
1370
1217
|
// the `needs` art_key item that drives the covered/action-needed banner.
|
|
1371
1218
|
await loadArtKey(me);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"ignoreRequests": ["/api/(gds|bongos)/"],
|
|
8
8
|
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed", "Password field"],
|
|
9
9
|
"states": {
|
|
10
|
-
"account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips"
|
|
10
|
+
"account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips"] } },
|
|
11
11
|
"access": { "auth": true, "url": "/settings#access", "actions": [["wait", 900]], "expect": { "visible": ["#cli-reissue-btn", "#sessions-list", "#artkey-card"] } },
|
|
12
12
|
"preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#wander-rows", "#render-rows"] } },
|
|
13
13
|
"sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#voices-table", "#sound-rows", "#event-sound-rows"] } },
|
|
@@ -334,7 +334,7 @@
|
|
|
334
334
|
// dropped where the module is off — and since task 1004296 it STAYS dropped: /deploy
|
|
335
335
|
// is the platform hall's page alone, and every other hall takes its update from
|
|
336
336
|
// Settings → Software update, whose Update button carries the hub hand-off.
|
|
337
|
-
{ id: 'deploy', label: 'Deploy', icon: 'admin', href: '/deploy', gate: 'archon', module: 'provisioning' },
|
|
337
|
+
{ id: 'deploy', label: 'Deploy', icon: 'admin', href: '/deploy', gate: 'archon', module: 'provisioning render-deploy' },
|
|
338
338
|
// task 1001949: what each hosted project costs. 'government' mirrors the metic floor
|
|
339
339
|
// of `provisioning.fleet.manage`, the atom its page and its one read check (the
|
|
340
340
|
// Deploy precedent above); dropped where the provisioning module is not enabled.
|
|
@@ -918,7 +918,9 @@
|
|
|
918
918
|
}
|
|
919
919
|
document.querySelectorAll('#app-sidebar [data-gate]').forEach(function (el) {
|
|
920
920
|
var ok = gateAllows(el.dataset.gate);
|
|
921
|
-
|
|
921
|
+
// A space-separated list is any-of: the Deploy item is the provisioning door's on the
|
|
922
|
+
// platform hall and the render-deploy module's on a Render project's hall (task 1004270).
|
|
923
|
+
if (ok && el.dataset.module && !el.dataset.module.split(' ').some(moduleOn)) ok = false;
|
|
922
924
|
el.hidden = !ok;
|
|
923
925
|
});
|
|
924
926
|
// AFTER the gate loop: applyCounts reads el.hidden, which the loop above is
|
|
@@ -109,12 +109,17 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
109
109
|
inset: 0;
|
|
110
110
|
pointer-events: none;
|
|
111
111
|
z-index: 0;
|
|
112
|
+
/* On a screen taller than the ~900px artboard the whole scene drops by half
|
|
113
|
+
the extra height, so it stays centred instead of sitting in the top 700px
|
|
114
|
+
over a strip of bare floor (task 1004344). Zero at or below the artboard, so
|
|
115
|
+
the design of record is unchanged there. % is of the room's own height. */
|
|
116
|
+
--room-drop: max(0px, calc((100% - 900px) / 2));
|
|
112
117
|
}
|
|
113
118
|
.st-room > *,
|
|
114
119
|
.st-window > * { position: absolute; }
|
|
115
120
|
.st-window {
|
|
116
121
|
left: max(calc(50% + 60px), 680px);
|
|
117
|
-
top: 86px;
|
|
122
|
+
top: calc(86px + var(--room-drop));
|
|
118
123
|
width: 560px;
|
|
119
124
|
height: 560px;
|
|
120
125
|
box-sizing: border-box;
|
|
@@ -151,22 +156,22 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
151
156
|
.st-light--7 { left: 250px; bottom: 120px; opacity: .5; }
|
|
152
157
|
.st-mullion { left: 264px; top: 0; width: 0; height: 100%; border-left: 12px solid var(--st-frame); }
|
|
153
158
|
.st-floor {
|
|
154
|
-
left: 0; right: 0; top: 700px; bottom: 0;
|
|
159
|
+
left: 0; right: 0; top: calc(700px + var(--room-drop)); bottom: 0;
|
|
155
160
|
min-height: 200px;
|
|
156
161
|
background: var(--st-floor);
|
|
157
162
|
border-top: 1px solid var(--st-floor-edge);
|
|
158
163
|
}
|
|
159
164
|
.st-glow {
|
|
160
|
-
left: calc(50% + 440px); top: 470px; width: 300px; height: 300px;
|
|
165
|
+
left: calc(50% + 440px); top: calc(470px + var(--room-drop)); width: 300px; height: 300px;
|
|
161
166
|
border-radius: 50%;
|
|
162
167
|
background: radial-gradient(circle, var(--st-glow) 0%, var(--st-glow-out) 65%);
|
|
163
168
|
}
|
|
164
|
-
.st-lamp-stem { left: calc(50% + 570px); top: 560px; width: 6px; height: 140px; background: var(--st-stem); }
|
|
165
|
-
.st-lamp-shade { left: calc(50% + 526px); top: 540px; width: 94px; height: 46px; border-radius: 48px 48px 6px 6px; background: var(--st-shade); }
|
|
166
|
-
.st-mug { left: calc(50% + 340px); top: 650px; width: 46px; height: 52px; border-radius: 0 0 10px 10px; background: var(--st-mug); }
|
|
169
|
+
.st-lamp-stem { left: calc(50% + 570px); top: calc(560px + var(--room-drop)); width: 6px; height: 140px; background: var(--st-stem); }
|
|
170
|
+
.st-lamp-shade { left: calc(50% + 526px); top: calc(540px + var(--room-drop)); width: 94px; height: 46px; border-radius: 48px 48px 6px 6px; background: var(--st-shade); }
|
|
171
|
+
.st-mug { left: calc(50% + 340px); top: calc(650px + var(--room-drop)); width: 46px; height: 52px; border-radius: 0 0 10px 10px; background: var(--st-mug); }
|
|
167
172
|
.st-leaf { border-radius: 50% 50% 0 0; }
|
|
168
|
-
.st-leaf--1 { left: calc(50% + 180px); top: 610px; width: 64px; height: 90px; background: var(--st-leaf-1); }
|
|
169
|
-
.st-leaf--2 { left: calc(50% + 220px); top: 590px; width: 50px; height: 110px; background: var(--st-leaf-2); }
|
|
173
|
+
.st-leaf--1 { left: calc(50% + 180px); top: calc(610px + var(--room-drop)); width: 64px; height: 90px; background: var(--st-leaf-1); }
|
|
174
|
+
.st-leaf--2 { left: calc(50% + 220px); top: calc(590px + var(--room-drop)); width: 50px; height: 110px; background: var(--st-leaf-2); }
|
|
170
175
|
|
|
171
176
|
/* Everything the artist reads or presses sits above the room. */
|
|
172
177
|
.st-top,
|
|
@@ -503,6 +508,7 @@ html.st-open { overflow: hidden; }
|
|
|
503
508
|
--------------------------------------------------------------------------- */
|
|
504
509
|
@media (max-width: 760px) {
|
|
505
510
|
.studio { padding: 20px 16px 140px; }
|
|
511
|
+
.st-room { --room-drop: 0px; }
|
|
506
512
|
.st-top { gap: 12px; }
|
|
507
513
|
.st-tally {
|
|
508
514
|
margin-left: 0;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
15
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
16
16
|
<link rel="stylesheet" href="/builders/studio.css?v=2026-09-28-tw16">
|
|
17
|
-
<link rel="stylesheet" href="/builders/approval-queue.css">
|
|
17
|
+
<link rel="stylesheet" href="/builders/approval-queue.css?v=2026-09-29-aqfix">
|
|
18
18
|
</head>
|
|
19
19
|
<!-- THE STUDIO HOME (task 1003971 / BV2.TW16, goal 1000095; ADR 0341 D2, D5,
|
|
20
20
|
D8 and D11). docs/design/mocks/tweak-mode/Main.dc.html is the master design:
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
179
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
180
180
|
<script src="/builders/palette.js" defer></script>
|
|
181
181
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
182
182
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -185,8 +185,9 @@
|
|
|
185
185
|
<script src="/builders/hall-kit.js"></script>
|
|
186
186
|
<!-- The Approval queue panel (task 1004323): its pure rules, then the panel.
|
|
187
187
|
studio.js mounts it with its own WRITE_ROUTES. -->
|
|
188
|
-
<script src="/builders/
|
|
189
|
-
<script src="/builders/approval-queue.js"></script>
|
|
188
|
+
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
189
|
+
<script src="/builders/approval-queue-lib.js?v=2026-09-29-aqfix"></script>
|
|
190
|
+
<script src="/builders/approval-queue.js?v=2026-09-29-aqfix"></script>
|
|
190
191
|
<!-- The page names and the draft body (the tweak editor's pure rules), then
|
|
191
192
|
the studio's own. -->
|
|
192
193
|
<script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -30,8 +30,11 @@ body[data-page="goal-map"], body[data-page="thinking"] .sky-stage {
|
|
|
30
30
|
--face: Manrope, "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
31
31
|
}
|
|
32
32
|
html, body { height: 100%; }
|
|
33
|
-
body[data-page="goal-map"], body[data-page="thinking"] .sky-stage { margin: 0; background: var(--void); color: var(--ink); font-family: var(--face); font-size: 15px; line-height: 1.5; overflow: hidden; -webkit-font-smoothing: antialiased; }
|
|
34
|
-
|
|
33
|
+
body[data-page="goal-map"], body[data-page="thinking"] .sky-stage { margin: 0; background: var(--void); color: var(--ink); font-family: var(--face); font-size: 15px; line-height: 1.5; overflow: hidden; overflow: clip; -webkit-font-smoothing: antialiased; }
|
|
34
|
+
/* clip, not just hidden (task 1004342): a hidden-overflow box is still scrollable, so
|
|
35
|
+
focus scrolled the stage sideways to show the footer hint and the whole sky slid
|
|
36
|
+
left. clip cuts the same pixels and cannot be scrolled; hidden stays as the fallback. */
|
|
37
|
+
body[data-page="thinking"] { overflow: hidden; overflow: clip; }
|
|
35
38
|
:where(body[data-page="goal-map"], .sky-stage) button { font-family: inherit; }
|
|
36
39
|
:where(body[data-page="goal-map"], .sky-stage) a { color: var(--accent); }
|
|
37
40
|
:where(body[data-page="goal-map"], .sky-stage) .lbl { font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); }
|
|
@@ -196,7 +199,7 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 64px; }
|
|
|
196
199
|
.obj { display: inline-flex; align-items: center; gap: 9px; border: 0; background: transparent; color: var(--ink-faint); font: var(--label); letter-spacing: .14em; text-transform: uppercase; cursor: pointer; padding: 6px 0; }
|
|
197
200
|
.obj:hover { color: var(--ink); }
|
|
198
201
|
.obj .g { width: 20px; height: 18px; display: grid; place-items: center; }
|
|
199
|
-
.alt { margin-left: auto; text-align: right; color: var(--ink-dim); font: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
202
|
+
.alt { margin-left: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; text-align: right; color: var(--ink-dim); font: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
200
203
|
.alt b { color: var(--ink-soft); font-weight: 400; }
|
|
201
204
|
.hint { color: var(--ink-ghost); }
|
|
202
205
|
.time { display: flex; align-items: center; gap: 8px; width: min(360px, 100%); align-self: center; padding: 3px 4px 3px 4px; border-radius: 999px; background: rgba(0,0,0,.45); box-shadow: inset 0 0 0 1px var(--rule-soft); opacity: .8; }
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
141
|
+
<script src="/builders/shell.js?v=2026-09-29-renderdeploy"></script>
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -148,7 +148,88 @@
|
|
|
148
148
|
return PAGE_ID_RE.test(id) ? id : null;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
// THE CHECK (task 1004385). A line the artist has read and is happy with,
|
|
152
|
+
// rewritten or not, carries a green check. It is the artist's own place-keeper
|
|
153
|
+
// and never leaves the browser: the server has no such field. Shift+Enter is
|
|
154
|
+
// the fast way through a page: check this line, land in the next.
|
|
155
|
+
const HINT = 'Shift + Enter checks a line and jumps to the next one. It is the quickest way through a page.';
|
|
156
|
+
|
|
157
|
+
function nextLineKey(lines, key) {
|
|
158
|
+
const list = lines || [];
|
|
159
|
+
const i = list.findIndex((l) => l.key === key);
|
|
160
|
+
return i >= 0 && i + 1 < list.length ? list[i + 1].key : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Only lines still on the page count, so a reading that moved cannot inflate it.
|
|
164
|
+
function confirmedCount(lines, confirmed) {
|
|
165
|
+
let n = 0;
|
|
166
|
+
for (const l of lines || []) if (confirmed && confirmed.has(l.key)) n += 1;
|
|
167
|
+
return n;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function hintText(checked, total) {
|
|
171
|
+
return `${HINT} ${checked} of ${total} checked.`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Remembered per page AND reading: a page that changed under the artist has
|
|
175
|
+
// new lines, and a stale check on L0007 would tick the wrong words.
|
|
176
|
+
function confirmStoreKey(pageId, readingHash) {
|
|
177
|
+
return `hall-tweak-checked:${pageId}:${readingHash}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseConfirmed(raw, lines) {
|
|
181
|
+
const known = new Set((lines || []).map((l) => l.key));
|
|
182
|
+
let arr = [];
|
|
183
|
+
try { arr = JSON.parse(raw || '[]'); } catch (_) { arr = []; }
|
|
184
|
+
return new Set(Array.isArray(arr) ? arr.filter((k) => known.has(k)) : []);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// SPLIT VIEW (task 1004386). The live site answers X-Frame-Options: SAMEORIGIN
|
|
188
|
+
// (set at the proxy in front of the app, not in this repo), so a page on
|
|
189
|
+
// another host than the editor's cannot be framed: a hall page can, a landing
|
|
190
|
+
// or status page cannot. For those the split view is a second window docked to
|
|
191
|
+
// the bottom half of the screen.
|
|
192
|
+
function canFrame(url, loc) {
|
|
193
|
+
try { return new URL(url, loc.href || loc.origin).origin === (loc.origin || new URL(loc.href).origin); } catch (_) { return false; }
|
|
194
|
+
}
|
|
195
|
+
function splitWindowFeatures(scr) {
|
|
196
|
+
const s = scr || {};
|
|
197
|
+
const w = s.availWidth || 1280;
|
|
198
|
+
const h = s.availHeight || 800;
|
|
199
|
+
const left = s.availLeft || 0;
|
|
200
|
+
const top = (s.availTop || 0) + Math.floor(h / 2);
|
|
201
|
+
return `popup=yes,width=${w},height=${Math.floor(h / 2)},left=${left},top=${top}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// THE SPLIT BUTTON AND THE DIVIDER (task 1004386, owner-amended). "open the
|
|
205
|
+
// page" repeats the artist's last choice; the arrow beside it offers both. The
|
|
206
|
+
// split's size is the share of the window the live page takes, kept between
|
|
207
|
+
// 20% and 80% so neither half can vanish.
|
|
208
|
+
const OPEN_WORDS = Object.freeze({ tab: 'open the page ↗', split: 'open in split view' });
|
|
209
|
+
function openChoice(raw) { return raw === 'split' ? 'split' : 'tab'; }
|
|
210
|
+
const SPLIT_MIN = 0.2;
|
|
211
|
+
const SPLIT_MAX = 0.8;
|
|
212
|
+
const SPLIT_STEP = 0.05;
|
|
213
|
+
function clampSplit(f) {
|
|
214
|
+
const n = Number(f);
|
|
215
|
+
if (f === null || f === undefined || f === '' || !Number.isFinite(n)) return 0.5;
|
|
216
|
+
return Math.min(SPLIT_MAX, Math.max(SPLIT_MIN, Math.round(n * 100) / 100));
|
|
217
|
+
}
|
|
218
|
+
// The divider sits on the frame's top edge, so the page's share is what lies below the pointer.
|
|
219
|
+
function splitFromPointer(clientY, innerHeight) {
|
|
220
|
+
if (!(innerHeight > 0)) return 0.5;
|
|
221
|
+
return clampSplit((innerHeight - clientY) / innerHeight);
|
|
222
|
+
}
|
|
223
|
+
// Up grows the live page, down shrinks it: the divider moves with the arrow.
|
|
224
|
+
function splitFromKey(current, key) {
|
|
225
|
+
if (key === 'ArrowUp') return clampSplit(clampSplit(current) + SPLIT_STEP);
|
|
226
|
+
if (key === 'ArrowDown') return clampSplit(clampSplit(current) - SPLIT_STEP);
|
|
227
|
+
if (key === 'Home') return SPLIT_MIN;
|
|
228
|
+
if (key === 'End') return SPLIT_MAX;
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const api = { OPEN_WORDS, openChoice, clampSplit, splitFromPointer, splitFromKey, canFrame, splitWindowFeatures, HINT, nextLineKey, confirmedCount, hintText, confirmStoreKey, parseConfirmed, NOTES, sectionsOf, isChanged, noteFor, draftBody, changedCount, counterText, initialValues, rowsFor, liveUrl, uploadedLabel, uploadRefusal, displayTitle, pageIdFrom };
|
|
152
233
|
if (typeof window !== 'undefined') window.OTBTweakEditor = api;
|
|
153
234
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
154
235
|
})();
|