@bongos/core 1.18.3 → 1.18.19
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 +1576 -286
- 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/migrate.sh +99 -14
- 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 +124 -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/migrate_root_resolution.mjs +124 -1
- 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
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
// roadmap.js — the Roadmap page (task
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// Board's "Versions, by progress" section (work.js) so the board stays about
|
|
5
|
-
// claimable work and this page owns the bigger picture. Server gate is
|
|
6
|
-
// SIGNEDIN_BUILDERS_PAGE_RE in serve-internal.js.
|
|
1
|
+
// roadmap.js — the Roadmap page, "Command deck" layout (task 1002426, owner-
|
|
2
|
+
// approved redesign from the mockup review; supersedes the flat progress-bar
|
|
3
|
+
// stack introduced in task 1757).
|
|
7
4
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
5
|
+
// Shape: a program-level hero (whole-build rollup) → versions GROUPED BY their
|
|
6
|
+
// real status (planning=Staged · building=In flight · frozen=Parked ·
|
|
7
|
+
// shipped=Shipped), with the dominant in-flight version featured full-width. The
|
|
8
|
+
// goal layer is SUMMARIZED per version — a status bar (achieved / in progress /
|
|
9
|
+
// not started) plus only the goals active right now — instead of nesting every
|
|
10
|
+
// goal + criterion inline (BONGOS-V1 alone carries ~30 goals; the old flat list
|
|
11
|
+
// was the big-picture killer). The full per-version goal list, inline join/leave,
|
|
12
|
+
// and Archon drag-to-reorder now live on the Goals page, which "See all N goals"
|
|
13
|
+
// links to.
|
|
14
|
+
//
|
|
15
|
+
// Reads (all GET): /public/progress (lifecycle rollup + counts + %, public),
|
|
16
|
+
// /public/versions (track + started/shipped dates, public), and — for every
|
|
17
|
+
// non-shipped version only — /goals?version=X + /goals/:id (per-goal criteria to
|
|
18
|
+
// split in-progress from not-started). Shipped versions render as a pill strip
|
|
19
|
+
// with no goal fetch. No writes. Server page gate: SIGNEDIN_BUILDERS_PAGE_RE.
|
|
14
20
|
(() => {
|
|
15
21
|
'use strict';
|
|
16
22
|
|
|
17
23
|
const API = '/api/bongos';
|
|
18
|
-
// Generated typed client (task 1996 / R09), served as window.BongosClient by
|
|
19
|
-
// /lib/bongos-client.js. baseUrl:'' passes the full same-origin paths this file
|
|
20
|
-
// builds through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
|
21
24
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
22
25
|
const { escapeHtml, $, fmtNum } = window.OTB;
|
|
23
26
|
const toast = (msg) => window.OTB.toast(msg, { duration: 2200, clear: true });
|
|
24
27
|
|
|
25
|
-
const viewer = { id: null, isArchon: false };
|
|
26
|
-
|
|
27
28
|
async function getJSON(path) {
|
|
28
29
|
const res = await api.request('GET', path);
|
|
29
30
|
if (res.status === 401) {
|
|
@@ -34,342 +35,354 @@
|
|
|
34
35
|
if (!res.ok) { const e = new Error(`${path} → ${res.status}`); e.status = res.status; e.data = data; throw e; }
|
|
35
36
|
return data;
|
|
36
37
|
}
|
|
37
|
-
async function postJSON(path, body) {
|
|
38
|
-
const res = await api.request('POST', path, { body: body || {} });
|
|
39
|
-
if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); throw new Error('redirecting to auth'); }
|
|
40
|
-
const data = res.data || {};
|
|
41
|
-
if (!res.ok) { const e = new Error(`${path} → ${res.status}`); e.status = res.status; e.data = data; throw e; }
|
|
42
|
-
return data;
|
|
43
|
-
}
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
40
|
+
const TRACK = {
|
|
41
|
+
product: { label: 'Product', cls: 'product' },
|
|
42
|
+
internal: { label: 'Platform', cls: 'internal' },
|
|
43
|
+
default: { label: 'Dogfood', cls: 'dogfood' },
|
|
44
|
+
};
|
|
45
|
+
const PHASE = {
|
|
46
|
+
building: { label: 'In flight', cls: 'building' },
|
|
47
|
+
planning: { label: 'Staged', cls: 'planning' },
|
|
48
|
+
frozen: { label: 'Parked', cls: 'frozen' },
|
|
49
|
+
};
|
|
50
|
+
// The instance's Goals page (root-relative — resolves on the builders host).
|
|
51
|
+
const GOALS_URL = '/goals';
|
|
52
|
+
const goalDeepLink = (id) => `/goals#/goal/${encodeURIComponent(String(id))}`;
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return `<span class="goal-badge ${cls}">${escapeHtml(status)}</span>`;
|
|
51
|
-
}
|
|
54
|
+
const pctOf = (n, d) => (d > 0 ? (n / d) * 100 : 0);
|
|
55
|
+
const ym = (s) => { if (!s) return ''; const d = new Date(s); return Number.isNaN(d.getTime()) ? '' : `${MON[d.getMonth()]} ${d.getFullYear()}`; };
|
|
56
|
+
const trackOf = (v) => TRACK[v.track] || TRACK.internal;
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const chip = (v) => { const t = trackOf(v); return `<span class="rm-chip rm-chip--${t.cls}"><span class="rm-sq"></span>${t.label}</span>`; };
|
|
59
|
+
const phase = (v) => { const p = PHASE[v.status]; return p ? `<span class="rm-phase rm-phase--${p.cls}"><span class="rm-pd"></span>${p.label}</span>` : ''; };
|
|
60
|
+
|
|
61
|
+
function ring(v) {
|
|
62
|
+
const done = v.pct >= 100;
|
|
63
|
+
return `<div class="rm-ring" style="--p:${v.pct};--rc:${done ? 'var(--ok)' : 'var(--accent)'}" role="img" aria-label="${v.pct}% of tasks complete"><b>${v.pct}<i>%</i></b></div>`;
|
|
64
|
+
}
|
|
65
|
+
function lbar(v) {
|
|
66
|
+
const t = v.total, l = v.life, r = Math.max(0, t - (l.sh + l.co + l.cm));
|
|
67
|
+
return `<div class="rm-lbar" role="img" aria-label="${l.sh} shipped, ${l.co} confirmed, ${l.cm} completed, ${r} remaining of ${t}">
|
|
68
|
+
<span class="rm-lbar__s rm-lbar__s--sh" style="width:${pctOf(l.sh, t)}%"></span>
|
|
69
|
+
<span class="rm-lbar__s rm-lbar__s--co" style="width:${pctOf(l.co, t)}%"></span>
|
|
70
|
+
<span class="rm-lbar__s rm-lbar__s--cm" style="width:${pctOf(l.cm, t)}%"></span></div>`;
|
|
71
|
+
}
|
|
72
|
+
function gbar(g) {
|
|
73
|
+
return `<div class="rm-gbar" role="img" aria-label="${g.ach} achieved, ${g.prog} in progress, ${g.plan} not started of ${g.total} goals">
|
|
74
|
+
<span class="rm-gbar__s rm-gbar__s--ach" style="width:${pctOf(g.ach, g.total)}%"></span>
|
|
75
|
+
<span class="rm-gbar__s rm-gbar__s--prog" style="width:${pctOf(g.prog, g.total)}%"></span>
|
|
76
|
+
<span class="rm-gbar__s rm-gbar__s--plan" style="width:${pctOf(g.plan, g.total)}%"></span></div>`;
|
|
61
77
|
}
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
? `<button type="button" class="vgoal-action vgoal-leave" data-act="leave" data-id="${escapeHtml(String(g.id))}">Leave</button>`
|
|
84
|
-
: (g.status === 'open'
|
|
85
|
-
? `<button type="button" class="vgoal-action vgoal-join" data-act="join" data-id="${escapeHtml(String(g.id))}">Join</button>`
|
|
86
|
-
: '');
|
|
87
|
-
// Priority is always visible ("sees order"); the drag handle is Archon-only
|
|
88
|
-
// ("can't change it" for everyone else) — the server-side rank gate on
|
|
89
|
-
// POST /goals/reorder is the real enforcement, this is just the affordance.
|
|
90
|
-
const handle = viewer.isArchon
|
|
91
|
-
? `<span class="vgoal__handle" draggable="true" title="Drag to reprioritize within ${escapeHtml(g.version_id || 'this version')}" aria-hidden="true">⠿</span>`
|
|
92
|
-
: '';
|
|
93
|
-
// A custom disclosure (not <details>/<summary>): the criteria toggle is its own
|
|
94
|
-
// button and Join/Leave is a plain sibling button — so neither click target is
|
|
95
|
-
// nested in a <summary>, where the native details-toggle makes buttons flaky.
|
|
96
|
-
return `
|
|
97
|
-
<div class="vgoal${achieved ? ' vgoal--achieved' : ''}${blocked ? ' vgoal--blocked' : ''}" data-goal-id="${escapeHtml(String(g.id))}" data-version-id="${escapeHtml(String(g.version_id || ''))}"${viewer.isArchon ? ' draggable="true"' : ''}>
|
|
98
|
-
<div class="vgoal__head">
|
|
99
|
-
${handle}
|
|
100
|
-
<span class="vgoal__priority" title="Priority within this version">${priority}</span>
|
|
101
|
-
<button type="button" class="vgoal__toggle" aria-expanded="${expanded ? 'true' : 'false'}">
|
|
102
|
-
<span class="vgoal__caret${expanded ? ' vgoal__caret--open' : ''}" aria-hidden="true">▸</span>
|
|
103
|
-
</button>
|
|
104
|
-
<a class="vgoal__title vgoal__title--link" href="/goals#/goal/${escapeHtml(String(g.id))}" title="Open goal #${escapeHtml(String(g.id))} detail">${escapeHtml(g.title || 'Untitled goal')}</a>
|
|
105
|
-
${goalBadge(g.status)}
|
|
106
|
-
${blockedBadge}
|
|
107
|
-
<span class="vgoal__prog">${sat}/${crit.length}</span>
|
|
108
|
-
${action}
|
|
109
|
-
</div>
|
|
110
|
-
<div class="vgoal__body"${expanded ? '' : ' hidden'}>
|
|
111
|
-
<span class="vgoal__err" data-err="${escapeHtml(String(g.id))}" hidden></span>
|
|
112
|
-
${critBlock}
|
|
113
|
-
</div>
|
|
79
|
+
const arrowSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>';
|
|
80
|
+
|
|
81
|
+
// The goals block that sits inside an in-flight / featured card. `active` are
|
|
82
|
+
// the in-progress goals (sat>0), already sorted; we show up to 4 and let
|
|
83
|
+
// "See all" carry the rest.
|
|
84
|
+
function goalsBlock(v) {
|
|
85
|
+
const g = v.goals;
|
|
86
|
+
if (!g || g.total === 0) {
|
|
87
|
+
return `<div class="rm-goals__frame"><div class="rm-goals__h"><span class="t">Goals</span></div>
|
|
88
|
+
<p class="rm-gnone">No goals declared yet — this version is tracked by its ${fmtNum(v.criteria)} done-when criteria.</p></div>`;
|
|
89
|
+
}
|
|
90
|
+
const active = g.active.slice(0, 4);
|
|
91
|
+
const rows = active.map((x) => {
|
|
92
|
+
const ready = x.total > 0 && x.sat >= x.total;
|
|
93
|
+
return `<div class="rm-goal">
|
|
94
|
+
<span class="rm-goal__dot"></span>
|
|
95
|
+
<a class="rm-goal__t" href="${goalDeepLink(x.id)}" title="Open goal #${escapeHtml(String(x.id))}">${escapeHtml(x.title || 'Untitled goal')}</a>
|
|
96
|
+
${ready ? '<span class="rm-goal__ready">criteria met</span>' : ''}
|
|
97
|
+
<span class="rm-goal__meter"><i style="width:${pctOf(x.sat, x.total)}%"></i></span>
|
|
98
|
+
<span class="rm-goal__n">${x.sat}/${x.total}</span>
|
|
114
99
|
</div>`;
|
|
100
|
+
}).join('');
|
|
101
|
+
return `<div class="rm-goals__frame">
|
|
102
|
+
<div class="rm-goals__h"><span class="t">Goals</span><span class="n">${g.total} total</span></div>
|
|
103
|
+
${gbar(g)}
|
|
104
|
+
<div class="rm-gdist">
|
|
105
|
+
<span><i class="rm-ach"></i><b>${g.ach}</b> achieved</span>
|
|
106
|
+
<span><i class="rm-prog"></i><b>${g.prog}</b> in progress</span>
|
|
107
|
+
<span><i class="rm-plan"></i><b>${g.plan}</b> not started</span>
|
|
108
|
+
</div>
|
|
109
|
+
${active.length ? `<div class="rm-ginp"><div class="rm-ginp__cap">Active right now</div>${rows}</div>`
|
|
110
|
+
: '<p class="rm-gnone" style="margin-top:12px">No goal has criteria met yet — work is in the task queue.</p>'}
|
|
111
|
+
<a class="rm-gmore" href="${GOALS_URL}">See all ${g.total} goals ${arrowSvg}</a>
|
|
112
|
+
</div>`;
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
115
|
+
// ---- card renderers -------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
function featCard(v) {
|
|
118
|
+
return `<article class="rm-card rm-card--feat">
|
|
119
|
+
<div class="rm-featgrid">
|
|
120
|
+
<div class="rm-featgrid__l">
|
|
121
|
+
<div class="rm-feat-top">
|
|
122
|
+
<div class="rt">
|
|
123
|
+
<div class="rm-row">${chip(v)}${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span></div>
|
|
124
|
+
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
125
|
+
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
126
|
+
</div>
|
|
127
|
+
${ring(v)}
|
|
128
|
+
</div>
|
|
129
|
+
<div class="rm-cardprog">
|
|
130
|
+
<div class="rm-count"><span><b>${fmtNum(v.shipped)}</b> of <b>${fmtNum(v.total)}</b> tasks</span><span>${fmtNum(v.criteria)} criteria · since ${ym(v.start)}</span></div>
|
|
131
|
+
${lbar(v)}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="rm-goals">${goalsBlock(v)}</div>
|
|
135
|
+
</div>
|
|
136
|
+
</article>`;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const cCompleted = Number(v.lifecycle_completed_count) || 0;
|
|
142
|
-
const pctOf = (n) => (total > 0 ? (n / total) * 100 : 0);
|
|
143
|
-
const pct = Number(v.percent_complete) || 0;
|
|
144
|
-
const tail = (cConfirmed + cCompleted > 0)
|
|
145
|
-
? ` · ${cShipped} shipped, ${cConfirmed} confirmed, ${cCompleted} completed`
|
|
146
|
-
: '';
|
|
147
|
-
const detailsBlock = renderGoals(goalsByVersion.get(v.version_id), v.done_when);
|
|
148
|
-
const badge = done
|
|
149
|
-
? ' <span class="vbar__done-badge" title="shipped — no work remains">shipped</span>'
|
|
150
|
-
: '';
|
|
151
|
-
return `
|
|
152
|
-
<div class="vbar${done ? ' vbar--done' : ''}">
|
|
153
|
-
<div class="vbar__top">
|
|
154
|
-
<span class="vbar__name">${escapeHtml(v.version_id)} · ${escapeHtml(v.name || '')}${badge}
|
|
155
|
-
</span>
|
|
156
|
-
<span class="vbar__count">${fmtNum(v.shipped_count)} of ${fmtNum(v.total_count)} shipped</span>
|
|
139
|
+
function flightCard(v) {
|
|
140
|
+
return `<article class="rm-card">
|
|
141
|
+
<div class="rm-feat-top">
|
|
142
|
+
<div class="rt">
|
|
143
|
+
<div class="rm-row">${chip(v)}${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span></div>
|
|
144
|
+
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
145
|
+
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
157
146
|
</div>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
147
|
+
${ring(v)}
|
|
148
|
+
</div>
|
|
149
|
+
<div class="rm-cardprog">
|
|
150
|
+
<div class="rm-count"><span><b>${fmtNum(v.shipped)}</b> of <b>${fmtNum(v.total)}</b> tasks</span><span>${fmtNum(v.criteria)} criteria · since ${ym(v.start)}</span></div>
|
|
151
|
+
${lbar(v)}
|
|
152
|
+
</div>
|
|
153
|
+
<div style="margin-top:13px">${goalsBlock(v)}</div>
|
|
154
|
+
</article>`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Staged (planning) card — goals-forward. No task progress yet, so it leads
|
|
158
|
+
// with the seeded plan instead of a lifecycle bar.
|
|
159
|
+
function stagedCard(v) {
|
|
160
|
+
const g = v.goals;
|
|
161
|
+
const seeded = (g && g.all ? g.all : []).slice(0, 4);
|
|
162
|
+
const rows = seeded.map((x) => `
|
|
163
|
+
<div class="rm-goal"><span class="rm-goal__dot" style="background:var(--rm-plan)"></span>
|
|
164
|
+
<a class="rm-goal__t" href="${goalDeepLink(x.id)}">${escapeHtml(x.title || 'Untitled goal')}</a>
|
|
165
|
+
<span class="rm-goal__seed">seeded</span></div>`).join('');
|
|
166
|
+
const totalGoals = g ? g.total : 0;
|
|
167
|
+
const goalsFrame = totalGoals === 0
|
|
168
|
+
? `<div class="rm-goals__frame"><div class="rm-goals__h"><span class="t">Goals</span></div>
|
|
169
|
+
<p class="rm-gnone">No goals seeded yet.</p></div>`
|
|
170
|
+
: `<div class="rm-goals__frame">
|
|
171
|
+
<div class="rm-goals__h"><span class="t">Goals</span><span class="n">${totalGoals} seeded</span></div>
|
|
172
|
+
${gbar(g)}
|
|
173
|
+
<div class="rm-gdist">
|
|
174
|
+
<span><i class="rm-ach"></i><b>${g.ach}</b> achieved</span>
|
|
175
|
+
<span><i class="rm-prog"></i><b>${g.prog}</b> in progress</span>
|
|
176
|
+
<span><i class="rm-plan"></i><b>${g.plan}</b> seeded</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="rm-ginp"><div class="rm-ginp__cap">The seeded plan</div>${rows}</div>
|
|
179
|
+
<a class="rm-gmore" href="${GOALS_URL}">See all ${totalGoals} goals ${arrowSvg}</a>
|
|
180
|
+
</div>`;
|
|
181
|
+
return `<article class="rm-card rm-card--staged" style="grid-column:1 / -1">
|
|
182
|
+
<div class="rm-featgrid">
|
|
183
|
+
<div class="rm-featgrid__l">
|
|
184
|
+
<div class="rm-row">${chip(v)}${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span></div>
|
|
185
|
+
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
186
|
+
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
187
|
+
<div class="rm-staged-state">
|
|
188
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v4M9 6l3-3 3 3"/><rect x="4" y="9" width="16" height="12" rx="2"/></svg>
|
|
189
|
+
<div><div class="s1">Goals seeded — awaiting kickoff</div>
|
|
190
|
+
<div class="s2"><b>${totalGoals}</b> goals · <b>${fmtNum(v.criteria)}</b> criteria planned · no tasks yet</div></div>
|
|
191
|
+
</div>
|
|
162
192
|
</div>
|
|
163
|
-
<div class="
|
|
164
|
-
${detailsBlock}
|
|
193
|
+
<div class="rm-goals">${goalsFrame}</div>
|
|
165
194
|
</div>
|
|
166
|
-
|
|
195
|
+
</article>`;
|
|
167
196
|
}
|
|
168
197
|
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
198
|
+
function parkedCard(v) {
|
|
199
|
+
return `<article class="rm-card rm-card--parked" style="grid-column:1 / -1">
|
|
200
|
+
<div class="rm-feat-top">
|
|
201
|
+
<div class="rt">
|
|
202
|
+
<div class="rm-row">${chip(v)}${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span></div>
|
|
203
|
+
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
204
|
+
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
205
|
+
</div>
|
|
206
|
+
${ring(v)}
|
|
207
|
+
</div>
|
|
208
|
+
<div class="rm-cardprog">
|
|
209
|
+
<div class="rm-count"><span><b>${fmtNum(v.shipped)}</b> of <b>${fmtNum(v.total)}</b> tasks</span><span>${fmtNum(v.criteria)} criteria · started ${ym(v.start)}</span></div>
|
|
210
|
+
${lbar(v)}
|
|
211
|
+
</div>
|
|
212
|
+
<div class="rm-parknote">
|
|
213
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="6" height="16" rx="1"/><rect x="14" y="4" width="6" height="16" rx="1"/></svg>
|
|
214
|
+
<div>Work is complete but the version is held — no tasks are being claimed here right now.</div>
|
|
215
|
+
</div>
|
|
216
|
+
</article>`;
|
|
217
|
+
}
|
|
183
218
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
219
|
+
function sectionHtml(dotCls, title, count, note, inner) {
|
|
220
|
+
return `<section class="rm-sec">
|
|
221
|
+
<div class="rm-sec__h"><span class="rm-sec__dot rm-sec__dot--${dotCls}"></span><span class="rm-sec__t">${title}</span><span class="rm-sec__n">${count}</span><span class="rm-sec__rule"></span></div>
|
|
222
|
+
${note ? `<p class="rm-gnone" style="margin:-4px 2px 13px">${note}</p>` : ''}
|
|
223
|
+
${inner}
|
|
224
|
+
</section>`;
|
|
225
|
+
}
|
|
187
226
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
227
|
+
function heroHtml(flight) {
|
|
228
|
+
const A = flight.reduce((a, v) => ({
|
|
229
|
+
total: a.total + v.total, shipped: a.shipped + v.shipped,
|
|
230
|
+
sh: a.sh + v.life.sh, co: a.co + v.life.co, cm: a.cm + v.life.cm,
|
|
231
|
+
}), { total: 0, shipped: 0, sh: 0, co: 0, cm: 0 });
|
|
232
|
+
const G = flight.reduce((a, v) => ({
|
|
233
|
+
total: a.total + (v.goals ? v.goals.total : 0), ach: a.ach + (v.goals ? v.goals.ach : 0),
|
|
234
|
+
prog: a.prog + (v.goals ? v.goals.prog : 0), plan: a.plan + (v.goals ? v.goals.plan : 0),
|
|
235
|
+
}), { total: 0, ach: 0, prog: 0, plan: 0 });
|
|
236
|
+
const heroPct = Math.round(pctOf(A.shipped, A.total));
|
|
237
|
+
const heroRem = A.total - A.shipped;
|
|
238
|
+
const goalsRow = G.total > 0 ? `
|
|
239
|
+
<div class="rm-hgoals">
|
|
240
|
+
<span class="glab">Goals</span>
|
|
241
|
+
<div class="rm-gbar">
|
|
242
|
+
<span class="rm-gbar__s rm-gbar__s--ach" style="width:${pctOf(G.ach, G.total)}%"></span>
|
|
243
|
+
<span class="rm-gbar__s rm-gbar__s--prog" style="width:${pctOf(G.prog, G.total)}%"></span>
|
|
244
|
+
<span class="rm-gbar__s rm-gbar__s--plan" style="width:${pctOf(G.plan, G.total)}%"></span>
|
|
245
|
+
</div>
|
|
246
|
+
<span class="gleg"><b>${G.ach}</b> achieved · <b>${G.prog}</b> in progress · <b>${G.plan}</b> not started <span style="color:var(--ink-faint)">of ${G.total}</span></span>
|
|
247
|
+
</div>` : '';
|
|
248
|
+
return `<div class="rm-hero">
|
|
249
|
+
<div class="rm-hero__top">
|
|
250
|
+
<div>
|
|
251
|
+
<div class="rm-big"><span class="n">${heroPct}</span><span class="u">%</span></div>
|
|
252
|
+
<div class="rm-lab">of all in-flight tasks shipped</div>
|
|
253
|
+
</div>
|
|
254
|
+
<div>
|
|
255
|
+
<div class="rm-lbar rm-hbar" role="img" aria-label="${A.sh} shipped, ${A.co} confirmed, ${A.cm} completed, ${heroRem} remaining of ${A.total}">
|
|
256
|
+
<span class="rm-lbar__s rm-lbar__s--sh" style="width:${pctOf(A.sh, A.total)}%"></span>
|
|
257
|
+
<span class="rm-lbar__s rm-lbar__s--co" style="width:${pctOf(A.co, A.total)}%"></span>
|
|
258
|
+
<span class="rm-lbar__s rm-lbar__s--cm" style="width:${pctOf(A.cm, A.total)}%"></span>
|
|
197
259
|
</div>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
260
|
+
<div class="rm-stats">
|
|
261
|
+
<div class="rm-stat"><span class="v">${flight.length}</span><span class="k">versions in flight</span></div>
|
|
262
|
+
<div class="rm-stat"><span class="v sh">${fmtNum(A.sh)}</span><span class="k">shipped (live)</span></div>
|
|
263
|
+
<div class="rm-stat"><span class="v co">${fmtNum(A.co)}</span><span class="k">confirmed</span></div>
|
|
264
|
+
<div class="rm-stat"><span class="v cm">${fmtNum(A.cm)}</span><span class="k">completed</span></div>
|
|
265
|
+
<div class="rm-stat"><span class="v">${fmtNum(heroRem)}</span><span class="k">tasks remaining</span></div>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
${goalsRow}
|
|
270
|
+
</div>`;
|
|
202
271
|
}
|
|
203
272
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
273
|
+
// ---- data ------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
// Enrich a version with its goal summary. `list` gives titles + status +
|
|
276
|
+
// sort_order (1 request); `/goals/:id` gives per-goal criteria (needed to split
|
|
277
|
+
// in-progress from not-started + to fill the "active now" list).
|
|
278
|
+
async function goalSummary(versionId) {
|
|
209
279
|
try {
|
|
210
280
|
const list = await getJSON(`${API}/goals?version=${encodeURIComponent(versionId)}`);
|
|
211
281
|
const goals = (list.goals || []).filter((g) => g.status !== 'archived');
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
282
|
+
if (!goals.length) return { total: 0, ach: 0, prog: 0, plan: 0, active: [], all: [] };
|
|
283
|
+
const details = await Promise.all(goals.map((g) => getJSON(`${API}/goals/${encodeURIComponent(g.id)}`).catch(() => null)));
|
|
284
|
+
const enriched = goals.map((g, i) => {
|
|
285
|
+
const d = details[i];
|
|
286
|
+
const crit = d && Array.isArray(d.criteria) ? d.criteria : [];
|
|
287
|
+
const sat = crit.filter((c) => c.satisfied).length;
|
|
288
|
+
return { id: g.id, title: g.title, status: g.status, sort: Number(g.sort_order) || 0, sat, total: crit.length };
|
|
289
|
+
}).sort((a, b) => a.sort - b.sort);
|
|
290
|
+
const ach = enriched.filter((g) => g.status === 'achieved').length;
|
|
291
|
+
const active = enriched.filter((g) => g.status !== 'achieved' && g.sat > 0);
|
|
292
|
+
const plan = enriched.filter((g) => g.status !== 'achieved' && g.sat === 0).length;
|
|
293
|
+
return { total: enriched.length, ach, prog: active.length, plan, active, all: enriched };
|
|
215
294
|
} catch (_) {
|
|
216
|
-
return [];
|
|
295
|
+
return { total: 0, ach: 0, prog: 0, plan: 0, active: [], all: [] };
|
|
217
296
|
}
|
|
218
297
|
}
|
|
219
298
|
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
299
|
+
// Merge /public/progress (counts, %, lifecycle) with /public/versions (track,
|
|
300
|
+
// dates) into one version model the renderers consume.
|
|
301
|
+
function mergeVersions(progress, versions) {
|
|
302
|
+
const meta = new Map();
|
|
303
|
+
(versions || []).forEach((v) => meta.set(v.id, v));
|
|
304
|
+
return (progress || []).map((p) => {
|
|
305
|
+
const m = meta.get(p.version_id) || {};
|
|
306
|
+
return {
|
|
307
|
+
id: p.version_id,
|
|
308
|
+
name: p.name || m.name || p.version_id,
|
|
309
|
+
track: m.track || 'internal',
|
|
310
|
+
status: p.status || m.status || 'building',
|
|
311
|
+
desc: (p.done_when || m.done_when || '').trim(),
|
|
312
|
+
shipped: Number(p.shipped_count) || 0,
|
|
313
|
+
total: Number(p.total_count) || 0,
|
|
314
|
+
pct: Number(p.percent_complete) || 0,
|
|
315
|
+
criteria: Number(p.criteria_count) || 0,
|
|
316
|
+
life: {
|
|
317
|
+
sh: Number(p.lifecycle_shipped_count) || 0,
|
|
318
|
+
co: Number(p.lifecycle_confirmed_count) || 0,
|
|
319
|
+
cm: Number(p.lifecycle_completed_count) || 0,
|
|
320
|
+
},
|
|
321
|
+
start: m.started_at || null,
|
|
322
|
+
ship: m.shipped_at || null,
|
|
323
|
+
};
|
|
324
|
+
});
|
|
235
325
|
}
|
|
236
326
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
if (errEl) { errEl.hidden = true; errEl.textContent = ''; }
|
|
245
|
-
try {
|
|
246
|
-
await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/${act}`);
|
|
247
|
-
toast(act === 'join' ? 'Joined the goal.' : 'Left the goal.');
|
|
248
|
-
// Re-render just this goal's row in place — join/leave changes only this goal's
|
|
249
|
-
// membership, so repainting (and collapsing) the whole version section would be
|
|
250
|
-
// both wasteful and jarring. Re-fetch the one goal for its fresh member list; the
|
|
251
|
-
// delegated #roadmap-versions handler catches clicks on the replacement button.
|
|
252
|
-
const detail = await getJSON(`${API}/goals/${encodeURIComponent(goalId)}`).catch(() => null);
|
|
253
|
-
if (detail && goalEl) {
|
|
254
|
-
// Join/leave never changes order — carry the row's current priority
|
|
255
|
-
// number over rather than re-deriving it (a single row has no view of
|
|
256
|
-
// its siblings to recompute a rank from).
|
|
257
|
-
const priority = goalEl.querySelector('.vgoal__priority')?.textContent || '';
|
|
258
|
-
goalEl.outerHTML = goalItemHtml(detail, priority);
|
|
259
|
-
} else if (goalEl) {
|
|
260
|
-
// Couldn't refetch — flip the button optimistically so the UI isn't stuck.
|
|
261
|
-
btn.outerHTML = act === 'join'
|
|
262
|
-
? `<button type="button" class="vgoal-action vgoal-leave" data-act="leave" data-id="${escapeHtml(String(goalId))}">Leave</button>`
|
|
263
|
-
: `<button type="button" class="vgoal-action vgoal-join" data-act="join" data-id="${escapeHtml(String(goalId))}">Join</button>`;
|
|
264
|
-
}
|
|
265
|
-
} catch (err) {
|
|
266
|
-
const code = err && err.data ? err.data.error : null;
|
|
267
|
-
const msg = code === 'archon_approval_required' ? 'This goal needs an Archon\'s approval to join.'
|
|
268
|
-
: code === 'goal_not_open' ? 'This goal is no longer open to join.'
|
|
269
|
-
: code === 'not_a_member' ? 'You are not a member of this goal.'
|
|
270
|
-
: (err && err.data && err.data.message) || `Could not ${act} the goal.`;
|
|
271
|
-
if (errEl) { errEl.textContent = msg; errEl.hidden = false; }
|
|
272
|
-
btn.disabled = false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
327
|
+
async function render() {
|
|
328
|
+
const root = $('#rm-root');
|
|
329
|
+
const [progress, versions] = await Promise.all([
|
|
330
|
+
getJSON(`${API}/public/progress`).then((d) => d.progress || []),
|
|
331
|
+
getJSON(`${API}/public/versions`).then((d) => d.versions || []).catch(() => []),
|
|
332
|
+
]);
|
|
333
|
+
const all = mergeVersions(progress, versions);
|
|
275
334
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// POST /goals/reorder; the server is the real gate (route-rank archon) — this
|
|
281
|
-
// is only reached by viewer.isArchon builders, matching the affordance.
|
|
282
|
-
let dragEl = null;
|
|
335
|
+
// Goal summaries only for versions we actually show goals on (everything but
|
|
336
|
+
// shipped) — a real perf win over fetching goals for the whole history.
|
|
337
|
+
const needGoals = all.filter((v) => v.status !== 'shipped');
|
|
338
|
+
await Promise.all(needGoals.map(async (v) => { v.goals = await goalSummary(v.id); }));
|
|
283
339
|
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (!el || !viewer.isArchon) return;
|
|
290
|
-
dragEl = el;
|
|
291
|
-
el.classList.add('vgoal--dragging');
|
|
292
|
-
e.dataTransfer.effectAllowed = 'move';
|
|
293
|
-
});
|
|
294
|
-
root.addEventListener('dragover', (e) => {
|
|
295
|
-
if (!dragEl) return;
|
|
296
|
-
const over = e.target.closest('.vgoal');
|
|
297
|
-
const list = dragEl.closest('.vgoal-list');
|
|
298
|
-
if (!over || !list || over === dragEl || !list.contains(over)) return;
|
|
299
|
-
e.preventDefault();
|
|
300
|
-
const rect = over.getBoundingClientRect();
|
|
301
|
-
const after = (e.clientY - rect.top) > rect.height / 2;
|
|
302
|
-
list.insertBefore(dragEl, after ? over.nextSibling : over);
|
|
303
|
-
});
|
|
304
|
-
root.addEventListener('drop', (e) => {
|
|
305
|
-
if (dragEl) e.preventDefault();
|
|
306
|
-
});
|
|
307
|
-
root.addEventListener('dragend', async () => {
|
|
308
|
-
if (!dragEl) return;
|
|
309
|
-
const el = dragEl;
|
|
310
|
-
const list = el.closest('.vgoal-list');
|
|
311
|
-
el.classList.remove('vgoal--dragging');
|
|
312
|
-
dragEl = null;
|
|
313
|
-
if (!list) return;
|
|
314
|
-
const versionId = el.dataset.versionId;
|
|
315
|
-
const rows = [...list.querySelectorAll('.vgoal')];
|
|
316
|
-
rows.forEach((row, i) => {
|
|
317
|
-
const num = row.querySelector('.vgoal__priority');
|
|
318
|
-
if (num) num.textContent = String(i + 1);
|
|
319
|
-
});
|
|
320
|
-
const orderedIds = rows.map((row) => Number(row.dataset.goalId));
|
|
321
|
-
try {
|
|
322
|
-
await postJSON(`${API}/goals/reorder`, { version_id: versionId, ordered_ids: orderedIds });
|
|
323
|
-
toast('Priority order saved.');
|
|
324
|
-
} catch (err) {
|
|
325
|
-
toast((err && err.data && err.data.message) || 'Could not save the new order — reloading.');
|
|
326
|
-
await loadVersionBars();
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
}
|
|
340
|
+
const flight = all.filter((v) => v.status === 'building').sort((a, b) => b.total - a.total);
|
|
341
|
+
const staged = all.filter((v) => v.status === 'planning').sort((a, b) => b.total - a.total);
|
|
342
|
+
const parked = all.filter((v) => v.status === 'frozen').sort((a, b) => b.pct - a.pct);
|
|
343
|
+
const shipped = all.filter((v) => v.status === 'shipped')
|
|
344
|
+
.sort((a, b) => (new Date(b.ship || 0)) - (new Date(a.ship || 0)));
|
|
330
345
|
|
|
331
|
-
|
|
346
|
+
let html = '';
|
|
332
347
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
348
|
+
if (flight.length) {
|
|
349
|
+
html += heroHtml(flight);
|
|
350
|
+
const feat = flight[0]; // dominant in-flight version (most tasks)
|
|
351
|
+
const rest = flight.slice(1);
|
|
352
|
+
const inner = featCard(feat) + (rest.length ? `<div class="rm-grid" style="margin-top:16px">${rest.map(flightCard).join('')}</div>` : '');
|
|
353
|
+
html += sectionHtml('flight', 'In flight', flight.length, '', inner);
|
|
354
|
+
} else {
|
|
355
|
+
html += `<div class="rm-hero"><div class="rm-lab" style="max-width:none">No version is in flight right now.</div></div>`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (staged.length) {
|
|
359
|
+
html += sectionHtml('staged', 'Staged', staged.length, 'Seeded with goals, not yet started.', `<div class="rm-grid">${staged.map(stagedCard).join('')}</div>`);
|
|
360
|
+
}
|
|
361
|
+
if (parked.length) {
|
|
362
|
+
html += sectionHtml('parked', 'Parked', parked.length, '', `<div class="rm-grid">${parked.map(parkedCard).join('')}</div>`);
|
|
363
|
+
}
|
|
364
|
+
if (shipped.length) {
|
|
365
|
+
const pills = shipped.map((v) => `<a class="rm-shippill" href="${GOALS_URL}" title="${escapeHtml(v.name)}"><span class="ok">✓</span><span class="nm">${escapeHtml(v.id)}</span><span class="mt">${fmtNum(v.shipped)} tasks · ${ym(v.ship)}</span></a>`).join('');
|
|
366
|
+
html += sectionHtml('shipped', 'Shipped', shipped.length, '', `<div class="rm-shipstrip">${pills}</div>`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!all.length) html = `<div class="profile__placeholder">No versions declared yet.</div>`;
|
|
370
|
+
root.innerHTML = html;
|
|
351
371
|
}
|
|
352
372
|
|
|
353
373
|
async function boot() {
|
|
354
|
-
// /me settles the viewer id + rank (so a joined goal shows "Leave" not
|
|
355
|
-
// "Join", and only an Archon sees the drag handle). Best-effort: a stale/
|
|
356
|
-
// expired session still renders the public progress.
|
|
357
|
-
try {
|
|
358
|
-
const me = await getJSON(`${API}/me`).catch(() => null);
|
|
359
|
-
viewer.id = me?.builder?.id ?? null;
|
|
360
|
-
viewer.isArchon = (me?.builder?.rank || '').toLowerCase() === 'archon';
|
|
361
|
-
} catch (_) { /* cosmetic only */ }
|
|
362
|
-
|
|
363
374
|
const sub = $('#roadmap-sub');
|
|
364
|
-
if (sub) sub.textContent = '
|
|
365
|
-
|
|
366
|
-
|
|
375
|
+
if (sub) sub.textContent = 'How the whole build is doing — the program at a glance, then each version. Shipped work stays out of the way.';
|
|
376
|
+
try {
|
|
377
|
+
await render();
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
380
|
+
console.error('[roadmap] load failed:', err);
|
|
381
|
+
const root = $('#rm-root');
|
|
382
|
+
if (root) root.innerHTML = `<div class="profile__placeholder">The roadmap could not be loaded right now. Reload to try again.</div>`;
|
|
383
|
+
toast('The roadmap failed to load. Reload to try again.');
|
|
384
|
+
}
|
|
367
385
|
}
|
|
368
386
|
|
|
369
|
-
|
|
370
|
-
boot().catch((err) => {
|
|
371
|
-
if (err && err.message === 'redirecting to auth') return;
|
|
372
|
-
console.error('[roadmap] load failed:', err);
|
|
373
|
-
toast('The roadmap failed to load. Reload to try again.');
|
|
374
|
-
});
|
|
387
|
+
boot();
|
|
375
388
|
})();
|