@bongos/core 1.18.3 → 1.18.18
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 +1575 -285
- package/.claude/skills/builder-box/SKILL.md +6 -0
- package/.claude/skills/builder-sequence/SKILL.md +97 -0
- package/.claude/skills/builder-ship/SKILL.md +8 -2
- package/.claude/skills/builder-start/SKILL.md +11 -0
- package/.claude/skills/builder-sync/SKILL.md +41 -0
- package/.claude/skills/fix-task/SKILL.md +64 -0
- package/.claude/skills/goal-create/SKILL.md +158 -0
- package/.claude/skills/goal-review/SKILL.md +3 -1
- package/.claude/skills/idea-triage/SKILL.md +14 -8
- package/.claude/skills/merge-mode/SKILL.md +2 -2
- package/.claude/skills/new-project/SKILL.md +1 -0
- package/.claude/skills/planning-session/SKILL.md +45 -18
- package/.env.local.example +4 -3
- package/README.md +4 -2
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +2 -2
- package/clients/bongos-client/bongos-client.global.js +82 -18
- package/clients/bongos-client/index.cjs +82 -18
- package/clients/bongos-client/index.d.ts +131 -29
- package/clients/bongos-client/index.mjs +82 -18
- package/config/branding.neutral.json +6 -1
- package/config/governance.neutral.json +21 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
- package/docs/adr/0090-metic-task-authoring.md +1 -1
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
- package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
- package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
- package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
- package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
- package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
- package/docs/adr/0152-metic-task-abandonment.md +67 -0
- package/docs/adr/0153-sequential-chain-runner.md +51 -0
- package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
- package/docs/adr/README.md +10 -0
- package/docs/api/openapi.json +843 -17
- package/docs/api-reference.md +23 -10
- package/docs/architecture.md +4 -2
- package/docs/branding-contract.md +3 -1
- package/docs/canonical-permissions.md +21 -8
- package/docs/file-map.md +8 -0
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
- package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
- package/docs/onboarding/diagrams/assertions.json +4 -7
- package/docs/onboarding/drachmae-and-karma.md +3 -3
- package/docs/onboarding/primer.md +17 -16
- package/docs/onboarding/slash-commands.md +124 -0
- package/docs/recipes/builder-box-lifecycle.md +23 -0
- package/docs/recipes/devbox-app-release.md +14 -4
- package/docs/recipes/instance-first-run.md +16 -0
- package/docs/recipes/local-dev.md +81 -0
- package/docs/recipes/ops-gotchas.md +19 -1
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/parallel-goal-waves.md +174 -0
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +48 -0
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
- package/migrations/003_pms.sql +6 -7
- package/migrations/006_pms_v2.sql +3 -31
- package/migrations/core_193_task_needs_rebase.sql +28 -0
- package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
- package/migrations/core_195_idea_triage_provenance.sql +57 -0
- package/migrations/core_196_session_reward_topup.sql +69 -0
- package/modules/autonomy/pollers/routine-timers.js +9 -8
- package/modules/builder-settings/wandering-prefs.js +61 -18
- package/modules/dev-box/app/README.md +73 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
- package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
- package/modules/dev-box/app/assets/tray/win-off.png +0 -0
- package/modules/dev-box/app/assets/tray/win-on.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
- package/modules/dev-box/app/build-resources/icon.png +0 -0
- package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
- package/modules/dev-box/app/electron-builder.yml +95 -0
- package/modules/dev-box/app/package-lock.json +5291 -0
- package/modules/dev-box/app/package.json +30 -0
- package/modules/dev-box/app/renderer/app.js +319 -0
- package/modules/dev-box/app/renderer/index.html +45 -0
- package/modules/dev-box/app/renderer/styles.css +246 -0
- package/modules/dev-box/app/renderer/theme.generated.css +16 -0
- package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
- package/modules/dev-box/app/scripts/gen-brand.js +138 -0
- package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
- package/modules/dev-box/app/scripts/gen-icons.js +286 -0
- package/modules/dev-box/app/scripts/vendor-client.js +23 -0
- package/modules/dev-box/app/src/box-controller.js +287 -0
- package/modules/dev-box/app/src/brand.generated.json +13 -0
- package/modules/dev-box/app/src/brand.js +16 -0
- package/modules/dev-box/app/src/config.js +19 -0
- package/modules/dev-box/app/src/gds-client.js +90 -0
- package/modules/dev-box/app/src/main.js +610 -0
- package/modules/dev-box/app/src/pairing.js +57 -0
- package/modules/dev-box/app/src/preload.js +29 -0
- package/modules/dev-box/app/src/session-store.js +58 -0
- package/modules/dev-box/app/src/ssh-setup.js +569 -0
- package/modules/dev-box/app/src/task-picker.js +70 -0
- package/modules/dev-box/app/src/tray.js +99 -0
- package/modules/dev-box/app/src/updater.js +55 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
- package/modules/dev-box/app/test/unit.mjs +526 -0
- package/modules/dev-box/box-access.js +121 -4
- package/modules/dev-box/boxes.js +20 -0
- package/modules/dev-box/module.json +1 -1
- package/modules/dev-box/routes/box.js +87 -3
- package/modules/economy/credits.js +116 -25
- package/modules/economy/reward.js +8 -0
- package/modules/economy/streak.js +2 -1
- package/modules/governance/CLAUDE.md +15 -0
- package/modules/governance/catalog.js +419 -0
- package/modules/governance/config.js +174 -0
- package/modules/governance/db.js +125 -0
- package/modules/governance/migrations/governance_001_tables.sql +81 -0
- package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
- package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
- package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
- package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
- package/modules/governance/module.json +10 -0
- package/modules/governance/protected-surfaces.js +88 -0
- package/modules/governance/protected-surfaces.json +80 -0
- package/modules/governance/rank-roles.js +83 -0
- package/modules/governance/resolver.js +221 -0
- package/modules/governance/routes/governance.js +89 -0
- package/modules/governance/routes/role-sync.js +49 -0
- package/modules/governance/session-scopes.js +127 -0
- package/modules/governance/session-scopes.json +38 -0
- package/modules/hall-ui/public/atlas.css +117 -0
- package/modules/hall-ui/public/atlas.html +3 -114
- package/modules/hall-ui/public/brand-apply.js +34 -6
- package/modules/hall-ui/public/builders.js +187 -219
- package/modules/hall-ui/public/diagrams.html +2 -0
- package/modules/hall-ui/public/dom-utils.js +27 -0
- package/modules/hall-ui/public/drachmae.html +2 -0
- package/modules/hall-ui/public/drachmae.js +54 -1
- package/modules/hall-ui/public/gate.css +98 -0
- package/modules/hall-ui/public/gate.html +3 -95
- package/modules/hall-ui/public/goal-inbox.js +4 -1
- package/modules/hall-ui/public/goals-page.js +626 -114
- package/modules/hall-ui/public/goals.css +79 -1
- package/modules/hall-ui/public/goals.html +38 -11
- package/modules/hall-ui/public/hall-widgets.js +62 -52
- package/modules/hall-ui/public/harbor.css +123 -0
- package/modules/hall-ui/public/harbor.html +3 -120
- package/modules/hall-ui/public/index.html +11 -4
- package/modules/hall-ui/public/instances-panel.js +4 -1
- package/modules/hall-ui/public/modules.css +63 -0
- package/modules/hall-ui/public/modules.html +3 -60
- package/modules/hall-ui/public/new-project-wizard.js +7 -3
- package/modules/hall-ui/public/not-ready.html +2 -0
- package/modules/hall-ui/public/pair.css +73 -0
- package/modules/hall-ui/public/pair.html +3 -70
- package/modules/hall-ui/public/palette.js +302 -0
- package/modules/hall-ui/public/primer.css +13 -0
- package/modules/hall-ui/public/primer.html +2 -0
- package/modules/hall-ui/public/profile.html +2 -0
- package/modules/hall-ui/public/ranks.html +2 -0
- package/modules/hall-ui/public/roadmap.css +179 -0
- package/modules/hall-ui/public/roadmap.html +9 -172
- package/modules/hall-ui/public/roadmap.js +329 -316
- package/modules/hall-ui/public/sessions.css +16 -0
- package/modules/hall-ui/public/sessions.html +3 -13
- package/modules/hall-ui/public/settings.html +2 -0
- package/modules/hall-ui/public/settings.js +4 -4
- package/modules/hall-ui/public/shell.js +143 -18
- package/modules/hall-ui/public/style.css +231 -39
- package/modules/hall-ui/public/task-detail.js +347 -26
- package/modules/hall-ui/public/task.html +6 -1
- package/modules/hall-ui/public/watch.html +2 -0
- package/modules/hall-ui/public/work.html +8 -6
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/ideas/inbox.js +61 -11
- package/modules/ideas/routes/inbox.js +23 -0
- package/modules/lifecycle/claim-eligibility.js +72 -0
- package/modules/lifecycle/db.js +519 -139
- package/modules/lifecycle/done-when.js +18 -3
- package/modules/lifecycle/github-push.js +19 -2
- package/modules/lifecycle/goal-advisory.js +114 -0
- package/modules/lifecycle/goal-authz.js +62 -0
- package/modules/lifecycle/lifecycle.js +9 -0
- package/modules/lifecycle/publish-reconciler.js +109 -6
- package/modules/lifecycle/routes/claims.js +68 -23
- package/modules/lifecycle/routes/done-when.js +110 -15
- package/modules/lifecycle/routes/goals.js +8 -38
- package/modules/lifecycle/routes/tasks.js +38 -6
- package/modules/lifecycle/ship-card.js +6 -0
- package/modules/onboarding/onboarding-state.js +1 -1
- package/modules/platform-identity/CLAUDE.md +15 -0
- package/modules/platform-identity/hub-device.js +147 -0
- package/modules/platform-identity/hub-keys.js +151 -0
- package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
- package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
- package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
- package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
- package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
- package/modules/platform-identity/module.json +18 -0
- package/modules/platform-identity/platform-identity.js +531 -0
- package/modules/platform-identity/pollers/code-sweep.js +43 -0
- package/modules/platform-identity/routes/my-projects.js +140 -0
- package/modules/platform-identity/routes/projects.js +107 -0
- package/modules/platform-identity/routes/scouting.js +35 -0
- package/modules/platform-identity/routes/sso.js +402 -0
- package/modules/platform-identity/tests/platform-identity.mjs +388 -0
- package/modules/platform-identity/ui/.gitkeep +0 -0
- package/modules/security/routes/reports.js +10 -0
- package/modules/sessions/db.js +166 -21
- package/modules/sessions/module.json +1 -1
- package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
- package/modules/sessions/routes/sessions.js +33 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/anim/idle/frame_00.webp +0 -0
- package/public-landing/assets/anim/idle/frame_01.webp +0 -0
- package/public-landing/assets/anim/idle/frame_02.webp +0 -0
- package/public-landing/assets/anim/idle/frame_03.webp +0 -0
- package/public-landing/assets/anim/idle/frame_04.webp +0 -0
- package/public-landing/assets/anim/idle/frame_05.webp +0 -0
- package/public-landing/assets/anim/idle/frame_06.webp +0 -0
- package/public-landing/assets/anim/idle/frame_07.webp +0 -0
- package/public-landing/assets/anim/idle/frame_08.webp +0 -0
- package/public-landing/assets/anim/idle/frame_09.webp +0 -0
- package/public-landing/assets/anim/idle/frame_10.webp +0 -0
- package/public-landing/assets/anim/idle/frame_11.webp +0 -0
- package/public-landing/assets/anim/idle/frame_12.webp +0 -0
- package/public-landing/assets/anim/idle/frame_13.webp +0 -0
- package/public-landing/assets/anim/idle/frame_14.webp +0 -0
- package/public-landing/assets/anim/idle/frame_15.webp +0 -0
- package/public-landing/assets/anim/idle/frame_16.webp +0 -0
- package/public-landing/assets/anim/idle/frame_17.webp +0 -0
- package/public-landing/assets/anim/idle/frame_18.webp +0 -0
- package/public-landing/assets/anim/idle/frame_19.webp +0 -0
- package/public-landing/assets/anim/idle/frame_20.webp +0 -0
- package/public-landing/assets/anim/idle/frame_21.webp +0 -0
- package/public-landing/assets/anim/idle/frame_22.webp +0 -0
- package/public-landing/assets/anim/idle/frame_23.webp +0 -0
- package/public-landing/assets/anim/idle/frame_24.webp +0 -0
- package/public-landing/assets/anim/idle/frame_25.webp +0 -0
- package/public-landing/assets/anim/idle/frame_26.webp +0 -0
- package/public-landing/assets/anim/idle/frame_27.webp +0 -0
- package/public-landing/assets/anim/idle/frame_28.webp +0 -0
- package/public-landing/assets/anim/idle/frame_29.webp +0 -0
- package/public-landing/assets/anim/idle/frame_30.webp +0 -0
- package/public-landing/assets/anim/idle/frame_31.webp +0 -0
- package/public-landing/assets/anim/idle/frame_32.webp +0 -0
- package/public-landing/assets/anim/idle/frame_33.webp +0 -0
- package/public-landing/assets/anim/idle/frame_34.webp +0 -0
- package/public-landing/assets/anim/idle/frame_35.webp +0 -0
- package/public-landing/assets/anim/idle/frame_36.webp +0 -0
- package/public-landing/assets/anim/idle/frame_37.webp +0 -0
- package/public-landing/assets/anim/idle/frame_38.webp +0 -0
- package/public-landing/assets/anim/idle/frame_39.webp +0 -0
- package/public-landing/assets/anim/idle/frame_40.webp +0 -0
- package/public-landing/assets/anim/idle/frame_41.webp +0 -0
- package/public-landing/assets/anim/idle/frame_42.webp +0 -0
- package/public-landing/assets/anim/idle/frame_43.webp +0 -0
- package/public-landing/create.html +51 -58
- package/public-landing/gate/README.md +137 -0
- package/public-landing/gate/dispmaps/bar.png +0 -0
- package/public-landing/gate/dispmaps/flat.png +0 -0
- package/public-landing/gate/dispmaps/key.png +0 -0
- package/public-landing/gate/index.html +587 -0
- package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
- package/public-landing/gate/previews/off-the-boats.jpg +0 -0
- package/public-landing/gate/space.css +799 -0
- package/public-landing/gate/space.js +1826 -0
- package/public-landing/gate/sprites/enter.png +0 -0
- package/public-landing/gate/sprites/search.png +0 -0
- package/public-landing/gate/sprites/terminal-blank.png +0 -0
- package/public-landing/gate/sprites/tile.png +0 -0
- package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
- package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
- package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
- package/public-landing/world/assets/plates/scene-establishing.png +0 -0
- package/scripts/gds/abandon.js +6 -4
- package/scripts/gds/api.js +10 -5
- package/scripts/gds/api.ps1 +26 -0
- package/scripts/gds/api.sh +6 -0
- package/scripts/gds/audit-ships.js +7 -1
- package/scripts/gds/backfill-sessions.js +78 -18
- package/scripts/gds/backup.js +23 -1
- package/scripts/gds/box-connect-lib.js +124 -31
- package/scripts/gds/box-sync.js +66 -0
- package/scripts/gds/box.js +225 -53
- package/scripts/gds/cf-tunnel.js +145 -6
- package/scripts/gds/claim.js +34 -3
- package/scripts/gds/cli-lib.js +161 -27
- package/scripts/gds/code.js +6 -2
- package/scripts/gds/deploy-divergence.js +209 -0
- package/scripts/gds/dev-lib.js +309 -0
- package/scripts/gds/dev.js +322 -0
- package/scripts/gds/doctor.js +90 -61
- package/scripts/gds/fetch-art-key.js +1 -1
- package/scripts/gds/fetch-sound-prefs.js +2 -2
- package/scripts/gds/fitness.js +228 -12
- package/scripts/gds/gds-db-backup.service +16 -1
- package/scripts/gds/gds-db-offsite.service +9 -1
- package/scripts/gds/gen-api-client.js +1 -1
- package/scripts/gds/gen-api-docs.js +36 -14
- package/scripts/gds/gen-diagrams.js +45 -28
- package/scripts/gds/gen-file-map.js +2 -1
- package/scripts/gds/gen-freshness.js +32 -0
- package/scripts/gds/gen-repo-map.js +3 -2
- package/scripts/gds/gen-session-index.js +14 -5
- package/scripts/gds/hooks/pre-push.js +2 -2
- package/scripts/gds/init.js +60 -3
- package/scripts/gds/install-git-hooks.js +1 -1
- package/scripts/gds/key-card.js +15 -6
- package/scripts/gds/key-status.js +1 -1
- package/scripts/gds/linkify-refs.js +35 -9
- package/scripts/gds/login.js +16 -1
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +123 -12
- package/scripts/gds/publish-manifest.js +7 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +4 -2
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
- package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
- package/scripts/gds/sequence.js +425 -0
- package/scripts/gds/session-cost.js +10 -1
- package/scripts/gds/setup.js +31 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +319 -29
- package/scripts/gds/smoke-gds.js +139 -8
- package/scripts/gds/start.js +171 -22
- package/scripts/gds/status.js +1 -1
- package/scripts/gds/triage.js +10 -9
- package/scripts/gds/upgrade.js +124 -2
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +447 -102
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +39 -0
- package/src/bongos/module-scope-map.js +27 -1
- package/src/bongos/permission-path-check.js +136 -102
- package/src/bongos/repo-info.js +47 -11
- package/src/bongos/route-rank-check.js +429 -46
- package/src/bongos/routes/_helpers.js +12 -1
- package/src/bongos/routes/auth.js +219 -2
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +32 -0
- package/src/instance-config.js +30 -0
- package/src/module-api.js +112 -1
- package/src/platform-server.js +86 -9
- package/tests/abandon_task.mjs +59 -3
- package/tests/api_cli_flags.mjs +53 -0
- package/tests/auth_page_gate.mjs +13 -0
- package/tests/backfill_sessions.mjs +127 -1
- package/tests/backup_cli.mjs +54 -0
- package/tests/bongos_box_connect.mjs +77 -0
- package/tests/box_access.mjs +130 -1
- package/tests/box_credential.mjs +125 -0
- package/tests/box_host_keys.mjs +24 -0
- package/tests/box_infra_resolve.mjs +149 -0
- package/tests/box_scope_session.mjs +6 -2
- package/tests/box_sync.mjs +55 -0
- package/tests/box_task_scope.mjs +99 -0
- package/tests/branding.mjs +12 -0
- package/tests/cf_ruleset_preflight.mjs +142 -0
- package/tests/cf_tunnel.mjs +37 -0
- package/tests/claim_gate_rebase.mjs +98 -0
- package/tests/clawback_confirmed_backlog.mjs +103 -0
- package/tests/cli_lib_formatters.mjs +20 -0
- package/tests/confirm_attribution.mjs +30 -46
- package/tests/credit_grant.mjs +198 -10
- package/tests/criterion_authoring_gate.mjs +272 -0
- package/tests/criterion_progress.mjs +33 -1
- package/tests/dev_lib.mjs +253 -0
- package/tests/devbox_artifact_gate.mjs +75 -0
- package/tests/devbox_dmg_backdrop.mjs +137 -0
- package/tests/devbox_icons.mjs +304 -0
- package/tests/devbox_installer_brand.mjs +232 -0
- package/tests/devbox_key_register.mjs +234 -0
- package/tests/docs_openapi_route.mjs +110 -0
- package/tests/doctor_hooks.mjs +141 -0
- package/tests/extract_json.mjs +140 -0
- package/tests/fitness.mjs +89 -0
- package/tests/gen_freshness.mjs +96 -0
- package/tests/goal_advisory.mjs +251 -0
- package/tests/goal_criterion_confirm_ui.mjs +269 -0
- package/tests/goal_routes.mjs +59 -4
- package/tests/goal_tier.mjs +12 -3
- package/tests/governance_catalog.mjs +129 -0
- package/tests/governance_claim_eligibility.mjs +262 -0
- package/tests/governance_config.mjs +187 -0
- package/tests/governance_ownership_scope.mjs +410 -0
- package/tests/governance_page_gates.mjs +324 -0
- package/tests/governance_parity.mjs +336 -0
- package/tests/governance_principals.mjs +232 -0
- package/tests/governance_protected_surfaces.mjs +208 -0
- package/tests/governance_require_permission.mjs +223 -0
- package/tests/governance_resolver.mjs +84 -0
- package/tests/governance_role_sync.mjs +231 -0
- package/tests/governance_seed.mjs +127 -0
- package/tests/governance_session_scope.mjs +434 -0
- package/tests/governance_vanilla_boot.mjs +203 -0
- package/tests/grade_attribution.mjs +7 -7
- package/tests/hall_audit.mjs +223 -0
- package/tests/hall_nav.mjs +191 -0
- package/tests/hall_palette.mjs +206 -0
- package/tests/hall_signout.mjs +124 -0
- package/tests/hook_user_agents.mjs +10 -0
- package/tests/idea_triage.mjs +162 -0
- package/tests/idp_device_exchange.mjs +93 -0
- package/tests/init.mjs +51 -0
- package/tests/instance_config.mjs +65 -0
- package/tests/instance_mismatch_guard.mjs +23 -6
- package/tests/instance_release_config.mjs +104 -0
- package/tests/linkify_refs.mjs +15 -2
- package/tests/module_contributions.mjs +55 -0
- package/tests/module_loader.mjs +10 -5
- package/tests/module_scope_active_claims.mjs +94 -0
- package/tests/needs_rebase.mjs +92 -0
- package/tests/one_active_claim_per_session.mjs +76 -0
- package/tests/override_attribution.mjs +10 -8
- package/tests/override_request_ownership.mjs +8 -4
- package/tests/parse_cookie_decode.mjs +42 -0
- package/tests/pay_on_land.mjs +129 -0
- package/tests/platform_boot.mjs +100 -1
- package/tests/post_login_landing.mjs +42 -0
- package/tests/preflight_checks.mjs +72 -0
- package/tests/provision.mjs +132 -7
- package/tests/publish_branch_route.mjs +53 -0
- package/tests/publish_reconciler.mjs +187 -2
- package/tests/repo_info_binding.mjs +248 -0
- package/tests/reward_gate.mjs +11 -2
- package/tests/reward_mode_cost_plus_only.mjs +116 -0
- package/tests/reward_mode_surfacing.mjs +168 -0
- package/tests/route_permission_check.mjs +301 -0
- package/tests/route_rank_check.mjs +254 -12
- package/tests/route_validation.mjs +30 -0
- package/tests/sequence.mjs +267 -0
- package/tests/session_digest.mjs +44 -0
- package/tests/session_index.mjs +18 -6
- package/tests/session_records.mjs +39 -1
- package/tests/session_reward_on_land.mjs +381 -0
- package/tests/session_reward_reconcile.mjs +229 -0
- package/tests/ship_api_client_pathspec.mjs +100 -0
- package/tests/ship_base_flag.mjs +93 -0
- package/tests/ship_ci_deploy.mjs +80 -3
- package/tests/ship_upstream_deletion.mjs +57 -0
- package/tests/smoke_target.mjs +233 -0
- package/tests/start_env_guard.mjs +87 -0
- package/tests/start_rebase_warning.mjs +103 -0
- package/tests/status_mirror_sync.mjs +122 -0
- package/tests/stop_refcheck.mjs +19 -1
- package/tests/task_goal_link.mjs +15 -6
- package/tests/upgrade.mjs +139 -0
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
|
@@ -109,31 +109,9 @@
|
|
|
109
109
|
return `${prefix}--${v > 0 ? 'up' : v < 0 ? 'down' : 'flat'}`;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
// the caller can slice. NOTE: the leaderboard pager deliberately does NOT use
|
|
116
|
-
// this — it never clamps and keeps its range/buttons always visible.
|
|
117
|
-
function renderPager(state, total, pageSize, { pagerEl, rangeEl, prevBtn, nextBtn }) {
|
|
118
|
-
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
|
119
|
-
if (state.page >= pageCount) state.page = pageCount - 1;
|
|
120
|
-
if (state.page < 0) state.page = 0;
|
|
121
|
-
const start = state.page * pageSize;
|
|
122
|
-
if (pagerEl) {
|
|
123
|
-
if (pageCount > 1) {
|
|
124
|
-
pagerEl.hidden = false;
|
|
125
|
-
if (rangeEl) {
|
|
126
|
-
const end = Math.min(start + pageSize, total);
|
|
127
|
-
rangeEl.textContent = `${start + 1}–${end} of ${total}`;
|
|
128
|
-
}
|
|
129
|
-
if (prevBtn) prevBtn.disabled = state.page === 0;
|
|
130
|
-
if (nextBtn) nextBtn.disabled = state.page >= pageCount - 1;
|
|
131
|
-
} else {
|
|
132
|
-
pagerEl.hidden = true;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return { start };
|
|
136
|
-
}
|
|
112
|
+
// (task 2174: the hand-rolled renderPager the ships + roster tables shared is
|
|
113
|
+
// gone — both now use the OTBKit pager, which clamps, persists per-page and
|
|
114
|
+
// paints the bar from one implementation.)
|
|
137
115
|
|
|
138
116
|
// ---- repo info (the clone URL the welcome walkthrough hands a newcomer) ---
|
|
139
117
|
//
|
|
@@ -248,6 +226,11 @@
|
|
|
248
226
|
// Standing-card "Action needed" banner survives re-renders that don't carry
|
|
249
227
|
// needs (same pattern as lastCostState).
|
|
250
228
|
let lastNeedsState = null;
|
|
229
|
+
// ADR 0120 part 5: cache the most recent /me `needs_rebase_tasks` so the
|
|
230
|
+
// Standing-card "Rebase owed" banner survives re-renders that don't carry it
|
|
231
|
+
// (same pattern as lastNeedsState). These are un-landed confirmed tasks that
|
|
232
|
+
// GATE new claims and whose reward is pending until they land on ship.
|
|
233
|
+
let lastRebaseState = null;
|
|
251
234
|
|
|
252
235
|
// Cached /me payload from the most recent loadAll() call. Used by the
|
|
253
236
|
// welcome-chip reopen path (#408) so we can re-render the welcome panel
|
|
@@ -300,30 +283,59 @@
|
|
|
300
283
|
// source of truth is now the hall-widget registry (window.OTB, dom-utils.js):
|
|
301
284
|
// hall-widgets.js registers the core widgets above (loads before this script),
|
|
302
285
|
// a module's client script registers its own (gated by loader.js + moduleOn).
|
|
303
|
-
//
|
|
304
|
-
// per-widget gates (
|
|
305
|
-
const SECTIONS = () => window.OTB.hallWidgets().map((wgt) => ({
|
|
306
|
-
id: wgt.id,
|
|
307
|
-
label: wgt.label,
|
|
308
|
-
view: wgt.view || 'home',
|
|
309
|
-
welcomeChip: !!wgt.welcomeChip,
|
|
310
|
-
archonOnly: !!wgt.archonOnly,
|
|
311
|
-
}));
|
|
286
|
+
// The rail (renderNav) reads window.OTB.hallWidgets() straight, applying the
|
|
287
|
+
// per-widget gates (archonOnly) carried on the registry entry.
|
|
312
288
|
|
|
313
289
|
// task 1723: which container a widget mounts into. A widget may declare
|
|
314
|
-
// `zone` (a fine-grained Home slot)
|
|
315
|
-
// leaderboard);
|
|
316
|
-
// widgets still land somewhere visible. Every
|
|
317
|
-
// so the `main .scroll` sweeps (newcomer gate,
|
|
290
|
+
// `zone` (a fine-grained Home slot), `group` (a Standing rail section, task
|
|
291
|
+
// 2174) or `view` (home/standing/leaderboard); all default to the Home main
|
|
292
|
+
// zone so pre-redesign module widgets still land somewhere visible. Every
|
|
293
|
+
// container lives INSIDE <main>, so the `main .scroll` sweeps (newcomer gate,
|
|
294
|
+
// landing hide-all) keep working.
|
|
318
295
|
const VIEW_CONTAINERS = {
|
|
319
296
|
home: 'zone-home-main',
|
|
320
297
|
standing: 'zone-standing',
|
|
321
|
-
achievements: 'zone-achievements',
|
|
322
298
|
leaderboard: 'zone-leaderboard',
|
|
323
299
|
};
|
|
300
|
+
|
|
301
|
+
// task 2174: Standing is an identity band + four rail sections grouped by the
|
|
302
|
+
// question they answer. This array is the rail's order and its labels; each
|
|
303
|
+
// entry gets a #standing-<key> anchor container inside #zone-standing that the
|
|
304
|
+
// group's widgets mount into. The Profile card declares no group, so it stays
|
|
305
|
+
// above them as the band.
|
|
306
|
+
const STANDING_GROUPS = [
|
|
307
|
+
{ key: 'work', label: 'Work' },
|
|
308
|
+
{ key: 'karma', label: 'Karma' },
|
|
309
|
+
{ key: 'progress', label: 'Progress' },
|
|
310
|
+
{ key: 'achievements', label: 'Achievements' },
|
|
311
|
+
];
|
|
312
|
+
const standingGroupId = (key) => `standing-${key}`;
|
|
313
|
+
|
|
314
|
+
// Create ALL four group containers the first time any group is asked for, so
|
|
315
|
+
// the rail's order is STANDING_GROUPS' — not whichever group's first widget
|
|
316
|
+
// happens to register earliest. Creating them on demand (rather than up front)
|
|
317
|
+
// is what keeps the Profile band on top: an ungrouped Standing widget appends
|
|
318
|
+
// straight to #zone-standing, so anything registered before the first grouped
|
|
319
|
+
// widget lands above the groups. Idempotent.
|
|
320
|
+
function ensureStandingGroups() {
|
|
321
|
+
const zone = document.getElementById('zone-standing');
|
|
322
|
+
if (!zone || document.getElementById(standingGroupId(STANDING_GROUPS[0].key))) return;
|
|
323
|
+
for (const g of STANDING_GROUPS) {
|
|
324
|
+
const el = document.createElement('div');
|
|
325
|
+
el.id = standingGroupId(g.key);
|
|
326
|
+
el.className = 'standing-group';
|
|
327
|
+
zone.appendChild(el);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
324
331
|
function widgetHost(wgt) {
|
|
325
332
|
const zone = wgt.zone ? document.getElementById(`zone-${wgt.zone}`) : null;
|
|
326
333
|
if (zone) return zone;
|
|
334
|
+
if (wgt.group) {
|
|
335
|
+
ensureStandingGroups();
|
|
336
|
+
const group = document.getElementById(standingGroupId(wgt.group));
|
|
337
|
+
if (group) return group;
|
|
338
|
+
}
|
|
327
339
|
const viewId = VIEW_CONTAINERS[wgt.view || 'home'] || VIEW_CONTAINERS.home;
|
|
328
340
|
return document.getElementById(viewId) || document.querySelector('main');
|
|
329
341
|
}
|
|
@@ -409,10 +421,11 @@
|
|
|
409
421
|
window.OTBBranding?.applyCurrencyLabels?.(main);
|
|
410
422
|
}
|
|
411
423
|
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
|
|
424
|
+
// task 2174: the Standing rail is an OTBKit sectionRail in scrollspy mode —
|
|
425
|
+
// the kit owns the IntersectionObserver that used to live here as
|
|
426
|
+
// setupNavObserver(). Built lazily on the first renderNav and refreshed after,
|
|
427
|
+
// so the same instance survives re-renders.
|
|
428
|
+
let standingRail = null;
|
|
416
429
|
|
|
417
430
|
// task 1723: the old sticky chip strip is gone. renderNav now settles the
|
|
418
431
|
// three nav surfaces the shell owns or hosts:
|
|
@@ -420,8 +433,10 @@
|
|
|
420
433
|
// the server enforces every page + endpoint regardless, ADR 0016);
|
|
421
434
|
// 2. the Home re-open affordance for a dismissed welcome banner (the old
|
|
422
435
|
// welcome chip's only job);
|
|
423
|
-
// 3. the Standing view's in-page section rail (#standing-rail)
|
|
424
|
-
//
|
|
436
|
+
// 3. the Standing view's in-page section rail (#standing-rail) — task 2174:
|
|
437
|
+
// one entry per GROUP that currently has something to show, not one per
|
|
438
|
+
// widget. A group with every member hidden (a newcomer, an empty karma
|
|
439
|
+
// feed) hides itself and drops off the rail.
|
|
425
440
|
// Keeps its (builder, onboarding) signature — all call sites pass them.
|
|
426
441
|
function renderNav(builder, onboarding) {
|
|
427
442
|
const isArchon = (builder?.rank || '').toLowerCase() === 'archon';
|
|
@@ -446,25 +461,30 @@
|
|
|
446
461
|
reopen.hidden = !(welcomeEl && welcomeEl.hidden && !graduated);
|
|
447
462
|
}
|
|
448
463
|
|
|
449
|
-
// Standing section rail: one link per visible
|
|
464
|
+
// Standing section rail: one link per group with a visible member.
|
|
450
465
|
const rail = document.getElementById('standing-rail');
|
|
451
466
|
if (rail) {
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
467
|
+
const shown = new Set(
|
|
468
|
+
window.OTB.hallWidgets()
|
|
469
|
+
.filter((wgt) => {
|
|
470
|
+
if (!wgt.group) return false;
|
|
471
|
+
if (wgt.archonOnly && !isArchon) return false;
|
|
472
|
+
const el = document.getElementById(wgt.id);
|
|
473
|
+
return !!el && !el.hidden;
|
|
474
|
+
})
|
|
475
|
+
.map((wgt) => wgt.group),
|
|
476
|
+
);
|
|
477
|
+
// An empty group would otherwise leave a gap in the panel column.
|
|
478
|
+
for (const g of STANDING_GROUPS) {
|
|
479
|
+
const el = document.getElementById(standingGroupId(g.key));
|
|
480
|
+
if (el) el.hidden = !shown.has(g.key);
|
|
481
|
+
}
|
|
482
|
+
const items = STANDING_GROUPS
|
|
483
|
+
.filter((g) => shown.has(g.key))
|
|
484
|
+
.map((g) => ({ id: standingGroupId(g.key), label: g.label }));
|
|
485
|
+
if (standingRail) standingRail.refresh(items);
|
|
486
|
+
else standingRail = window.OTBKit.makeSectionRail({ mount: rail, items, mode: 'scrollspy' });
|
|
462
487
|
}
|
|
463
|
-
|
|
464
|
-
// Rebuild the observer to track whichever sections are currently in
|
|
465
|
-
// the rail. Doing this on each renderNav call covers both initial load
|
|
466
|
-
// and post-dismiss/re-open re-renders.
|
|
467
|
-
setupNavObserver();
|
|
468
488
|
}
|
|
469
489
|
|
|
470
490
|
// #748: newcomer gate. A Xenos sees a deliberately spare hall — their
|
|
@@ -502,56 +522,6 @@
|
|
|
502
522
|
// gates. This function keeps only the widget-section sweep.
|
|
503
523
|
}
|
|
504
524
|
|
|
505
|
-
function setupNavObserver() {
|
|
506
|
-
if (navObserver) {
|
|
507
|
-
navObserver.disconnect();
|
|
508
|
-
navObserver = null;
|
|
509
|
-
}
|
|
510
|
-
const list = document.getElementById('standing-rail');
|
|
511
|
-
if (!list) return;
|
|
512
|
-
|
|
513
|
-
const chipsBySection = new Map();
|
|
514
|
-
list.querySelectorAll('a[data-section]').forEach((a) => {
|
|
515
|
-
chipsBySection.set(a.dataset.section, a);
|
|
516
|
-
});
|
|
517
|
-
if (chipsBySection.size === 0) return;
|
|
518
|
-
|
|
519
|
-
const sections = Array.from(chipsBySection.keys())
|
|
520
|
-
.map((id) => document.getElementById(id))
|
|
521
|
-
.filter((el) => el && !el.hidden);
|
|
522
|
-
|
|
523
|
-
if (sections.length === 0) return;
|
|
524
|
-
|
|
525
|
-
// rootMargin trims the active band to roughly the upper third of the
|
|
526
|
-
// viewport (top 40% ignored, bottom 55% ignored — a ~5% horizontal
|
|
527
|
-
// strip about a third of the way down the page). The active flip
|
|
528
|
-
// happens when a section headline crosses that band, not when the
|
|
529
|
-
// section first peeks in. Matches the rationale in the task spec.
|
|
530
|
-
navObserver = new IntersectionObserver((entries) => {
|
|
531
|
-
for (const entry of entries) {
|
|
532
|
-
if (entry.isIntersecting) {
|
|
533
|
-
const id = entry.target.id;
|
|
534
|
-
chipsBySection.forEach((chip, sectionId) => {
|
|
535
|
-
chip.classList.toggle('rail-link--active', sectionId === id);
|
|
536
|
-
});
|
|
537
|
-
// Auto-scroll the active chip into view on mobile so the user
|
|
538
|
-
// can see what section they're in without having to scroll the
|
|
539
|
-
// nav strip themselves.
|
|
540
|
-
const active = chipsBySection.get(id);
|
|
541
|
-
if (active && window.matchMedia('(max-width: 640px)').matches) {
|
|
542
|
-
active.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
|
543
|
-
}
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}, {
|
|
548
|
-
rootMargin: '-40% 0px -55% 0px',
|
|
549
|
-
threshold: 0,
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
sections.forEach((sec) => navObserver.observe(sec));
|
|
553
|
-
}
|
|
554
|
-
|
|
555
525
|
function reopenWelcomeFromNav(e) {
|
|
556
526
|
e.preventDefault();
|
|
557
527
|
const id = window.__buildersSelfId;
|
|
@@ -712,6 +682,7 @@
|
|
|
712
682
|
const karma = lastKarmaState;
|
|
713
683
|
const cost = lastCostState;
|
|
714
684
|
const needs = lastNeedsState;
|
|
685
|
+
const rebase = lastRebaseState;
|
|
715
686
|
const avatar = builder.avatar_url
|
|
716
687
|
? `<img class="profile__avatar" src="${escapeHtml(builder.avatar_url)}" alt="">`
|
|
717
688
|
: `<div class="profile__avatar" aria-hidden="true"></div>`;
|
|
@@ -770,7 +741,20 @@
|
|
|
770
741
|
</div>`
|
|
771
742
|
: '';
|
|
772
743
|
|
|
744
|
+
// ADR 0120 part 5: the "Rebase owed" banner. `needs_rebase_tasks` are un-landed
|
|
745
|
+
// confirmed tasks the publish-reconciler flagged — they GATE new claims (part 3)
|
|
746
|
+
// and their per-task reward is PENDING until they land on ship (part 1). Rendered
|
|
747
|
+
// above the other banners since it blocks the queue; reads the SAME /me field the
|
|
748
|
+
// CLI (/builder-start) does. `task N` form, never a bare #N (the ref rule).
|
|
749
|
+
const rebaseTasks = Array.isArray(rebase) ? rebase.filter((t) => t && t.id != null) : [];
|
|
750
|
+
const rebaseBanner = rebaseTasks.length
|
|
751
|
+
? `<div class="profile__rebase" role="status">
|
|
752
|
+
<strong>Rebase owed — new claims paused</strong> — ${rebaseTasks.length} confirmed ${rebaseTasks.length === 1 ? 'task' : 'tasks'} can't land yet, so ${rebaseTasks.length === 1 ? 'its' : 'their'} reward is pending (it lands on ship). Rebase + re-ship or release ${rebaseTasks.length === 1 ? 'it' : 'them'}: ${rebaseTasks.map((t) => 'task ' + escapeHtml(String(t.id))).join(', ')}.
|
|
753
|
+
</div>`
|
|
754
|
+
: '';
|
|
755
|
+
|
|
773
756
|
$('#profile-body').innerHTML = `
|
|
757
|
+
${rebaseBanner}
|
|
774
758
|
${needsBanner}
|
|
775
759
|
${budgetBanner}
|
|
776
760
|
${avatar}
|
|
@@ -824,6 +808,7 @@
|
|
|
824
808
|
const homeStanding = document.getElementById('home-standing-body');
|
|
825
809
|
if (homeStanding) {
|
|
826
810
|
homeStanding.innerHTML = `
|
|
811
|
+
${rebaseBanner}
|
|
827
812
|
${needsBanner}
|
|
828
813
|
${budgetBanner}
|
|
829
814
|
<div class="profile profile--compact">
|
|
@@ -1329,11 +1314,10 @@
|
|
|
1329
1314
|
// filtered, sorted, and paged entirely client-side. Mirrors the status site's
|
|
1330
1315
|
// Lately Inscribed pattern (modules/status-ui/public/status.js renderShippedPage), with
|
|
1331
1316
|
// an additional version filter since a builder's history spans versions.
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
};
|
|
1317
|
+
const shipState = { all: [] };
|
|
1318
|
+
// task 2174: paging moved to the OTBKit pager (per-page select + persistence);
|
|
1319
|
+
// 10 stays the default page size this table has always used.
|
|
1320
|
+
const shipsPager = window.OTBKit.makePager('ships', 'otb.standing.shipsPerPage', { defaultPerPage: 10 });
|
|
1337
1321
|
|
|
1338
1322
|
function filterShips(ships) {
|
|
1339
1323
|
const win = $('#f-ships-window')?.value || 'all';
|
|
@@ -1380,9 +1364,6 @@
|
|
|
1380
1364
|
const tbody = $('#recent-ships tbody');
|
|
1381
1365
|
const countEl = $('#ships-count');
|
|
1382
1366
|
const pagerEl = $('#ships-pager');
|
|
1383
|
-
const rangeEl = $('#ships-range');
|
|
1384
|
-
const prevBtn = $('#ships-prev');
|
|
1385
|
-
const nextBtn = $('#ships-next');
|
|
1386
1367
|
|
|
1387
1368
|
const filtered = filterShips(shipState.all);
|
|
1388
1369
|
const mode = $('#f-ships-sort')?.value || 'recent';
|
|
@@ -1426,8 +1407,7 @@
|
|
|
1426
1407
|
return;
|
|
1427
1408
|
}
|
|
1428
1409
|
|
|
1429
|
-
const
|
|
1430
|
-
const slice = sorted.slice(start, start + SHIPS_PAGE_SIZE);
|
|
1410
|
+
const slice = window.OTBKit.paginate(sorted, shipsPager);
|
|
1431
1411
|
|
|
1432
1412
|
tbody.innerHTML = slice.map((s) => `
|
|
1433
1413
|
<tr>
|
|
@@ -1442,11 +1422,11 @@
|
|
|
1442
1422
|
`).join('');
|
|
1443
1423
|
}
|
|
1444
1424
|
|
|
1445
|
-
// Re-render when filters change. Reset to page
|
|
1425
|
+
// Re-render when filters change. Reset to page 1 so the new filter starts at
|
|
1446
1426
|
// the top — otherwise switching to a smaller window can leave you on page 9
|
|
1447
1427
|
// of a 2-page result.
|
|
1448
1428
|
function onShipsFilterChange() {
|
|
1449
|
-
|
|
1429
|
+
shipsPager.page = 1;
|
|
1450
1430
|
renderRecentShips();
|
|
1451
1431
|
}
|
|
1452
1432
|
|
|
@@ -1723,12 +1703,17 @@
|
|
|
1723
1703
|
// isn't a peer's work.
|
|
1724
1704
|
|
|
1725
1705
|
let acclaimCanVote = false;
|
|
1706
|
+
// task 1002446: this was the hall's last unpaged list. The endpoint returns the
|
|
1707
|
+
// whole feed in one call, so the rows are held here and the kit's client-side
|
|
1708
|
+
// pager slices them — a page change re-renders without a refetch.
|
|
1709
|
+
const acclaimState = { tasks: [] };
|
|
1710
|
+
const acclaimPager = window.OTBKit.makePager('acclaim', 'otb.standing.acclaimPerPage', { defaultPerPage: 10 });
|
|
1726
1711
|
|
|
1727
1712
|
function renderAcclaimFeed(data) {
|
|
1728
1713
|
const scroll = $('#acclaim-scroll');
|
|
1729
|
-
|
|
1730
|
-
if (!scroll || !tbody) return;
|
|
1714
|
+
if (!scroll) return;
|
|
1731
1715
|
const tasks = (data && Array.isArray(data.tasks)) ? data.tasks : [];
|
|
1716
|
+
acclaimState.tasks = tasks;
|
|
1732
1717
|
acclaimCanVote = !!(data && data.can_vote);
|
|
1733
1718
|
if (tasks.length === 0) {
|
|
1734
1719
|
// Nothing to acclaim yet (e.g. the only confirmed work so far is your
|
|
@@ -1738,7 +1723,18 @@
|
|
|
1738
1723
|
}
|
|
1739
1724
|
// #748: a gated newcomer never shows the acclaim feed.
|
|
1740
1725
|
scroll.hidden = newcomerGated;
|
|
1741
|
-
|
|
1726
|
+
// task 2174: this feed lands after loadAll built the rail, and it can be the
|
|
1727
|
+
// only visible member of the Karma group (an empty karma feed hides itself).
|
|
1728
|
+
// Re-run renderNav so the group — and its rail entry — appear.
|
|
1729
|
+
if (lastMeState?.builder) renderNav(lastMeState.builder, lastMeState.onboarding || null);
|
|
1730
|
+
renderAcclaimRows();
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function renderAcclaimRows() {
|
|
1734
|
+
const tbody = $('#acclaim-feed tbody');
|
|
1735
|
+
if (!tbody) return;
|
|
1736
|
+
const slice = window.OTBKit.paginate(acclaimState.tasks, acclaimPager);
|
|
1737
|
+
tbody.innerHTML = slice.map((t) => {
|
|
1742
1738
|
const by = escapeHtml(t.display_name || (t.github_login ? `@${t.github_login}` : `builder #${t.shipped_by}`));
|
|
1743
1739
|
const net = Number(t.net_votes) || 0;
|
|
1744
1740
|
const netCls = signClasses(net, 'acclaim__net');
|
|
@@ -1797,6 +1793,12 @@
|
|
|
1797
1793
|
buttons.forEach((b) => { b.disabled = true; });
|
|
1798
1794
|
try {
|
|
1799
1795
|
const res = await postJSON(`${API}/tasks/${encodeURIComponent(taskId)}/vote`, { vote });
|
|
1796
|
+
// task 1002446: the row is now paged, so the vote has to land in the held
|
|
1797
|
+
// rows too — otherwise flipping to page 2 and back re-renders the stale
|
|
1798
|
+
// server tally over the one just cast.
|
|
1799
|
+
acclaimState.tasks = acclaimState.tasks.map((t) => (Number(t.id) === taskIdNum
|
|
1800
|
+
? { ...t, net_votes: res.net_votes, my_vote: res.my_vote }
|
|
1801
|
+
: t));
|
|
1800
1802
|
// Reflect the authoritative net + which button is active.
|
|
1801
1803
|
const netEl = $(`[data-acclaim-net="${taskId}"]`);
|
|
1802
1804
|
if (netEl) {
|
|
@@ -1823,32 +1825,10 @@
|
|
|
1823
1825
|
// the hall's old Claim section + its renderTasks/copy-prompt machinery were
|
|
1824
1826
|
// removed. work.js carries the equivalent rendering for that page.
|
|
1825
1827
|
|
|
1826
|
-
//
|
|
1827
|
-
|
|
1828
|
-
//
|
|
1829
|
-
//
|
|
1830
|
-
// the colouring is deterministic regardless of payload ordering.
|
|
1831
|
-
function renderBountyTable(rows) {
|
|
1832
|
-
const tbody = $('#bounty-table tbody');
|
|
1833
|
-
if (!tbody) return;
|
|
1834
|
-
if (!Array.isArray(rows) || !rows.length) {
|
|
1835
|
-
tbody.innerHTML = emptyRow(3, 'The reward schedule could not be loaded.');
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
tbody.innerHTML = rows.map((r) => {
|
|
1839
|
-
const sev = escapeHtml(r.severity || '');
|
|
1840
|
-
const overridden = r.overridden
|
|
1841
|
-
? ` <span style="color:var(--ink-faint);font-size:0.82rem">(tuned)</span>`
|
|
1842
|
-
: '';
|
|
1843
|
-
return `
|
|
1844
|
-
<tr>
|
|
1845
|
-
<td style="text-transform:capitalize;font-weight:600">${sev}</td>
|
|
1846
|
-
<td class="ledger__drachmae">${fmtNum(r.drachmae)}${overridden}</td>
|
|
1847
|
-
<td style="color:var(--ink-soft)">${escapeHtml(r.blurb || '')}</td>
|
|
1848
|
-
</tr>
|
|
1849
|
-
`;
|
|
1850
|
-
}).join('');
|
|
1851
|
-
}
|
|
1828
|
+
// (task 2174: the red-team bounty rate card — V3.R65 #284 — left Standing for
|
|
1829
|
+
// /drachmae. It is a published reward SCHEDULE, i.e. reference material that
|
|
1830
|
+
// reads the same for every builder, not part of anyone's personal standing.
|
|
1831
|
+
// drachmae.js now fetches /public/bounty-table and renders it there.)
|
|
1852
1832
|
|
|
1853
1833
|
// ---- idea-inbox triage cue (task 1471) -----------------------------------
|
|
1854
1834
|
|
|
@@ -1872,13 +1852,18 @@
|
|
|
1872
1852
|
const kind = i.kind
|
|
1873
1853
|
? `<span class="triage-inbox__kind">${escapeHtml(i.kind)}</span>`
|
|
1874
1854
|
: '';
|
|
1855
|
+
// task 1002447: an explicit verb, matching the Accept/Decline affordance
|
|
1856
|
+
// the goal slice puts on its own rows. It opens the record overlay, where
|
|
1857
|
+
// promote/merge/discard live — the forms need more room than a summary
|
|
1858
|
+
// row has, and the placement doctrine keeps this card a summary.
|
|
1875
1859
|
return `<li class="triage-inbox__item">`
|
|
1876
1860
|
+ `<a class="triage-inbox__link" href="#/idea/${encodeURIComponent(i.id)}">${title}</a>`
|
|
1877
1861
|
+ kind
|
|
1862
|
+
+ `<button class="btn-ghost" type="button" data-triage-idea="${escapeHtml(String(i.id))}">Triage</button>`
|
|
1878
1863
|
+ `</li>`;
|
|
1879
1864
|
}).join('');
|
|
1880
1865
|
const more = openCount > 5
|
|
1881
|
-
? `<p class="ginbox__pending">…${fmtNum(openCount - 5)} more open ideas —
|
|
1866
|
+
? `<p class="ginbox__pending">…${fmtNum(openCount - 5)} more open ideas — triage clears them one at a time.</p>`
|
|
1882
1867
|
: '';
|
|
1883
1868
|
box.innerHTML = `<h3 class="ginbox__sec-h">Ideas awaiting triage <span class="count-pill">${fmtNum(openCount)}</span></h3>`
|
|
1884
1869
|
+ `<ul class="triage-inbox">${rows}</ul>${more}`;
|
|
@@ -1886,6 +1871,30 @@
|
|
|
1886
1871
|
window.OTBAttention?.refresh();
|
|
1887
1872
|
}
|
|
1888
1873
|
|
|
1874
|
+
// task 1002447: refetch the slice so a verdict advances the queue in place —
|
|
1875
|
+
// the next open ideas move up into the five visible rows instead of the card
|
|
1876
|
+
// sitting stale until a reload. Metic+ only, exactly as the initial load is:
|
|
1877
|
+
// loadAll sets the flag from the /me it already has, so both paths read one
|
|
1878
|
+
// gate and a non-triager never issues an /inbox request at all.
|
|
1879
|
+
let canTriage = false;
|
|
1880
|
+
function loadTriageInbox() {
|
|
1881
|
+
if (!canTriage) return;
|
|
1882
|
+
getJSON(`${API}/inbox`)
|
|
1883
|
+
.then((data) => renderTriageInbox(data))
|
|
1884
|
+
.catch(() => { /* nice-to-have; never break the hall */ });
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// task-detail.js announces a landed verdict from the record overlay; the row
|
|
1888
|
+
// it acted on is one of ours.
|
|
1889
|
+
window.addEventListener('otb:idea-triaged', loadTriageInbox);
|
|
1890
|
+
|
|
1891
|
+
// The row's explicit verb: open the record overlay, where the three triage
|
|
1892
|
+
// forms live. Delegated so it survives every re-render of the slice.
|
|
1893
|
+
document.addEventListener('click', (e) => {
|
|
1894
|
+
const btn = e.target.closest('[data-triage-idea]');
|
|
1895
|
+
if (btn) window.location.hash = `#/idea/${encodeURIComponent(btn.dataset.triageIdea)}`;
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1889
1898
|
// ---- Home "Next up" slice (task 2184) -------------------------------------
|
|
1890
1899
|
|
|
1891
1900
|
// Fill the top of the "Your work" card with the first two rows of the
|
|
@@ -1950,12 +1959,12 @@
|
|
|
1950
1959
|
|
|
1951
1960
|
const lbState = {
|
|
1952
1961
|
rows: [],
|
|
1953
|
-
page: 0,
|
|
1954
|
-
pageSize: 25, // task 1750: top-100 Roll — 25/page keeps it to a few pages
|
|
1955
|
-
|
|
1956
1962
|
sortBy: 'drachmae', // 'rank' | 'drachmae' | 'karma' | 'tokens'
|
|
1957
1963
|
sortDir: 'desc', // 'desc' | 'asc'
|
|
1958
1964
|
};
|
|
1965
|
+
// task 2174: OTBKit pager. 25/page is what task 1750 set for the top-100 Roll;
|
|
1966
|
+
// the per-page select now lets a builder widen it and the choice persists.
|
|
1967
|
+
const lbPager = window.OTBKit.makePager('lb', 'otb.standing.leaderboardPerPage', { defaultPerPage: 25 });
|
|
1959
1968
|
|
|
1960
1969
|
// Stable sort. Server returns rows in drachmae-DESC order, which is also the
|
|
1961
1970
|
// default view; for other modes (rank, karma, drachmae-ASC), re-sort here.
|
|
@@ -2051,9 +2060,9 @@
|
|
|
2051
2060
|
|
|
2052
2061
|
function renderLeaderboard() {
|
|
2053
2062
|
const rows = sortedLeaderboardRows();
|
|
2054
|
-
|
|
2055
|
-
const
|
|
2056
|
-
const
|
|
2063
|
+
// paginate() clamps the page, slices, and paints the whole #lb-* bar.
|
|
2064
|
+
const slice = window.OTBKit.paginate(rows, lbPager);
|
|
2065
|
+
const start = (lbPager.page - 1) * lbPager.perPage;
|
|
2057
2066
|
const tbody = $('#leaderboard tbody');
|
|
2058
2067
|
|
|
2059
2068
|
if (!slice.length) {
|
|
@@ -2094,12 +2103,6 @@
|
|
|
2094
2103
|
: 'none';
|
|
2095
2104
|
head.setAttribute('aria-sort', ariaSort);
|
|
2096
2105
|
}
|
|
2097
|
-
|
|
2098
|
-
$('#lb-range').textContent = rows.length
|
|
2099
|
-
? `${start + 1}–${Math.min(end, rows.length)} of ${rows.length}`
|
|
2100
|
-
: '0';
|
|
2101
|
-
$('#lb-prev').disabled = lbState.page === 0;
|
|
2102
|
-
$('#lb-next').disabled = end >= rows.length;
|
|
2103
2106
|
}
|
|
2104
2107
|
|
|
2105
2108
|
// Cycle: pick column → desc → asc → back to default (drachmae desc).
|
|
@@ -2120,7 +2123,7 @@
|
|
|
2120
2123
|
lbState.sortBy = key;
|
|
2121
2124
|
lbState.sortDir = 'desc';
|
|
2122
2125
|
}
|
|
2123
|
-
|
|
2126
|
+
lbPager.page = 1;
|
|
2124
2127
|
renderLeaderboard();
|
|
2125
2128
|
}
|
|
2126
2129
|
|
|
@@ -2188,6 +2191,9 @@
|
|
|
2188
2191
|
lastKarmaState = me.karma || lastKarmaState;
|
|
2189
2192
|
lastCostState = me.cost || lastCostState;
|
|
2190
2193
|
lastNeedsState = me.needs || lastNeedsState;
|
|
2194
|
+
// ADR 0120 part 5: owed-rebase list is authoritative on every /me (an empty
|
|
2195
|
+
// array means "nothing owed now"), so take it verbatim when present.
|
|
2196
|
+
if (Array.isArray(me.needs_rebase_tasks)) lastRebaseState = me.needs_rebase_tasks;
|
|
2191
2197
|
renderOnboarding(me.onboarding || null, me.discord);
|
|
2192
2198
|
// If they graduated while watching, hide the welcome panel + rebuild the nav
|
|
2193
2199
|
// (drops the Welcome/Path chips) — the same transition loadAll would render.
|
|
@@ -2218,6 +2224,7 @@
|
|
|
2218
2224
|
lastKarmaState = me.karma || null;
|
|
2219
2225
|
lastCostState = me.cost || null;
|
|
2220
2226
|
lastNeedsState = me.needs || null; // task 1013 / ADR 0073: action-needed banner
|
|
2227
|
+
lastRebaseState = Array.isArray(me.needs_rebase_tasks) ? me.needs_rebase_tasks : null; // ADR 0120 part 5: owed-rebase banner
|
|
2221
2228
|
// #408: stash the whole /me payload so reopenWelcomeFromNav can re-render
|
|
2222
2229
|
// the welcome panel without a fresh round-trip.
|
|
2223
2230
|
lastMeState = me;
|
|
@@ -2275,22 +2282,18 @@
|
|
|
2275
2282
|
// task 1471: idea-inbox triage cue — Metic+ only (triage is a Metic+ action).
|
|
2276
2283
|
// Fetched fire-and-forget so a slow/large inbox can't hold up the hall; the
|
|
2277
2284
|
// widget is hiddenByDefault, so a non-triager never loads or sees idea data.
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
.then((data) => renderTriageInbox(data))
|
|
2281
|
-
.catch(() => { /* nice-to-have; never break the hall */ });
|
|
2282
|
-
}
|
|
2285
|
+
canTriage = ['metic', 'archon'].includes(normalizeRank(me.builder?.rank));
|
|
2286
|
+
loadTriageInbox();
|
|
2283
2287
|
|
|
2284
2288
|
// Then the rest, in parallel. Any individual section failure shouldn't take
|
|
2285
2289
|
// down the page — log and show an inline placeholder.
|
|
2286
2290
|
// #726: the per-skill model picker (Voices) moved to /builders/settings,
|
|
2287
2291
|
// so the hall no longer fetches /me/skill-prefs here.
|
|
2288
|
-
const [achAll, ships, versionsResp, lb
|
|
2292
|
+
const [achAll, ships, versionsResp, lb] = await Promise.allSettled([
|
|
2289
2293
|
getJSON(`${API}/achievements`),
|
|
2290
2294
|
getJSON(`${API}/me/recent-ships?limit=all`),
|
|
2291
2295
|
getJSON(`${API}/versions`),
|
|
2292
2296
|
getJSON(`${API}/leaderboard`),
|
|
2293
|
-
getJSON(`${API}/public/bounty-table`),
|
|
2294
2297
|
]);
|
|
2295
2298
|
|
|
2296
2299
|
if (achAll.status === 'fulfilled') {
|
|
@@ -2323,7 +2326,7 @@
|
|
|
2323
2326
|
|
|
2324
2327
|
if (lb.status === 'fulfilled') {
|
|
2325
2328
|
lbState.rows = lb.value.leaderboard || [];
|
|
2326
|
-
|
|
2329
|
+
lbPager.page = 1;
|
|
2327
2330
|
renderLeaderboard();
|
|
2328
2331
|
// #757: the Standing card painted before the Roll loaded, so re-render it
|
|
2329
2332
|
// now that lbState.rows exists — this is when its "#N on the Roll" stat
|
|
@@ -2332,13 +2335,6 @@
|
|
|
2332
2335
|
} else {
|
|
2333
2336
|
$('#leaderboard tbody').innerHTML = emptyRow(5, 'The leaderboard could not be loaded.');
|
|
2334
2337
|
}
|
|
2335
|
-
|
|
2336
|
-
if (bounty.status === 'fulfilled') {
|
|
2337
|
-
renderBountyTable(bounty.value.table || []);
|
|
2338
|
-
} else {
|
|
2339
|
-
$('#bounty-table tbody').innerHTML = emptyRow(3, 'The reward schedule could not be loaded.');
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
2338
|
}
|
|
2343
2339
|
|
|
2344
2340
|
function wireEvents() {
|
|
@@ -2408,22 +2404,11 @@
|
|
|
2408
2404
|
$('#f-ships-window')?.addEventListener('change', onShipsFilterChange);
|
|
2409
2405
|
$('#f-ships-version')?.addEventListener('change', onShipsFilterChange);
|
|
2410
2406
|
$('#f-ships-sort')?.addEventListener('change', renderRecentShips);
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
renderRecentShips();
|
|
2417
|
-
});
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
$('#lb-prev').addEventListener('click', () => {
|
|
2421
|
-
if (lbState.page > 0) { lbState.page--; renderLeaderboard(); }
|
|
2422
|
-
});
|
|
2423
|
-
$('#lb-next').addEventListener('click', () => {
|
|
2424
|
-
const max = Math.ceil(lbState.rows.length / lbState.pageSize) - 1;
|
|
2425
|
-
if (lbState.page < max) { lbState.page++; renderLeaderboard(); }
|
|
2426
|
-
});
|
|
2407
|
+
// task 2174: the kit wires prev/next/per-page for both tables against the
|
|
2408
|
+
// #ships-*/#lb-* ids its own pagerBarHtml() stamped in hall-widgets.js.
|
|
2409
|
+
window.OTBKit.wirePager(shipsPager, renderRecentShips);
|
|
2410
|
+
window.OTBKit.wirePager(lbPager, renderLeaderboard);
|
|
2411
|
+
window.OTBKit.wirePager(acclaimPager, renderAcclaimRows);
|
|
2427
2412
|
|
|
2428
2413
|
// Sortable leaderboard headers — clickable + keyboard-operable. Each
|
|
2429
2414
|
// sortable column registers under LB_SORT_HEADERS; the click handler
|
|
@@ -2442,28 +2427,11 @@
|
|
|
2442
2427
|
});
|
|
2443
2428
|
}
|
|
2444
2429
|
|
|
2445
|
-
//
|
|
2446
|
-
//
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
await api.request('POST', `${API}/auth/logout`);
|
|
2451
|
-
} catch (_) { /* even if it fails we drop the cookie below */ }
|
|
2452
|
-
// Best-effort cookie clear on the client side too. Mirror the SERVER
|
|
2453
|
-
// logout (src/bongos/routes/auth.js): expire BOTH the current `gds_session`
|
|
2454
|
-
// and the legacy `pms_session`, each in BOTH the host-only form and the
|
|
2455
|
-
// apex-scoped `Domain=.amazonprimea.com` form (#726 — the session cookie
|
|
2456
|
-
// is set apex-scoped on production hosts so it crosses subdomains; an
|
|
2457
|
-
// expire only matches the cookie whose Domain attribute it names, so the
|
|
2458
|
-
// host-only Max-Age=0 alone leaves the apex cookie live). Clearing only
|
|
2459
|
-
// `pms_session` host-only — the prior behaviour — left a real
|
|
2460
|
-
// `gds_session` cookie behind on this offline fallback path.
|
|
2461
|
-
for (const name of ['gds_session', 'pms_session']) {
|
|
2462
|
-
document.cookie = `${name}=; Max-Age=0; path=/`;
|
|
2463
|
-
document.cookie = `${name}=; Max-Age=0; path=/; Domain=.${location.hostname.split('.').slice(-2).join('.')}`;
|
|
2464
|
-
}
|
|
2465
|
-
window.location.assign('/');
|
|
2466
|
-
});
|
|
2430
|
+
// Sign-out is wired centrally in shell.js (doLogout + a delegated click
|
|
2431
|
+
// listener on the sign-out anchors), so it runs the real POST -> cookie-clear
|
|
2432
|
+
// -> bounce-home on EVERY hall page, not only this index. It used to live
|
|
2433
|
+
// here alone, which is exactly why sign-out silently no-op'd everywhere else
|
|
2434
|
+
// (a bare GET to the POST-only endpoint; task 1002245). Nothing to wire here.
|
|
2467
2435
|
}
|
|
2468
2436
|
|
|
2469
2437
|
// ---- Live updates (#569 / ADR 0030 → R55 / ADR 0069) -------------------
|
|
@@ -107,6 +107,8 @@
|
|
|
107
107
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
108
108
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
109
109
|
<script src="/builders/shell.js?v=2026-07-02-shell1"></script>
|
|
110
|
+
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
111
|
+
<script src="/builders/palette.js" defer></script>
|
|
110
112
|
<script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
|
|
111
113
|
<script src="/builders/copy-on-click.js?v=2026-06-01-copy-1" defer></script>
|
|
112
114
|
</body>
|