@bongos/core 1.18.0 → 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 +1740 -640
- 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 +5 -3
- 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 +11 -2
- 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/0142-project-context-is-prose-only.md +41 -0
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
- 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 +15 -2
- package/docs/api/openapi.json +2648 -483
- package/docs/api-reference.md +73 -13
- 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 +15 -7
- 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/packaging-the-core.md +13 -0
- 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/upgrading-the-core.md +5 -2
- 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/039_newcomer_friendly_column.sql +5 -0
- package/migrations/102_remove_box_first_provision_approval.sql +4 -0
- package/migrations/core_192_github_oauth_tokens.sql +33 -0
- 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/my-projects.html +78 -0
- package/modules/hall-ui/public/my-projects.js +142 -0
- 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/scouting.html +45 -0
- package/modules/hall-ui/public/scouting.js +73 -0
- 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/modules/status-ui/public/explore.html +54 -0
- package/modules/status-ui/public/explore.js +67 -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 +252 -62
- 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 -21
- 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/lint-migrations.js +102 -0
- package/scripts/gds/login.js +58 -11
- package/scripts/gds/mirror-redact.js +5 -0
- package/scripts/gds/package-core.js +263 -4
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +268 -19
- package/scripts/gds/publish-manifest.js +22 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +42 -16
- 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 +72 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +433 -59
- 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 +271 -7
- package/scripts/migrate.sh +10 -0
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +809 -107
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +105 -0
- package/src/bongos/github-repos.js +93 -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 +312 -13
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/github.js +90 -0
- package/src/bongos/routes/instance.js +15 -3
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/routes.js +2 -0
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +49 -2
- package/src/instance-config.js +46 -0
- package/src/module-api.js +140 -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 +41 -23
- 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/github_repos_helpers.mjs +91 -0
- package/tests/github_token_store.mjs +121 -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 +198 -0
- package/tests/idp_federation.mjs +100 -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/migrations_forward_safe.mjs +86 -0
- package/tests/module_api.mjs +1 -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/package_core.mjs +246 -1
- 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 +289 -7
- package/tests/pruned_lockfile_integrity.mjs +79 -0
- 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 +140 -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 +393 -5
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
- package/.gitignore +0 -91
- package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
- package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
- package/scripts/hall-preview/fixtures/access-requests.json +0 -20
- package/scripts/hall-preview/fixtures/achievements.json +0 -76
- package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
- package/scripts/hall-preview/fixtures/audit-log.json +0 -68
- package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
- package/scripts/hall-preview/fixtures/box__me.json +0 -5
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
- package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
- package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
- package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
- package/scripts/hall-preview/fixtures/goals.json +0 -502
- package/scripts/hall-preview/fixtures/goals__25.json +0 -96
- package/scripts/hall-preview/fixtures/goals__26.json +0 -67
- package/scripts/hall-preview/fixtures/goals__3.json +0 -103
- package/scripts/hall-preview/fixtures/goals__35.json +0 -49
- package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
- package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
- package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
- package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
- package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
- package/scripts/hall-preview/fixtures/inbox.json +0 -330
- package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
- package/scripts/hall-preview/fixtures/instance.json +0 -124
- package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
- package/scripts/hall-preview/fixtures/me.json +0 -476
- package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
- package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
- package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
- package/scripts/hall-preview/fixtures/me__render.json +0 -5
- package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
- package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
- package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
- package/scripts/hall-preview/fixtures/override-requests.json +0 -15
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
- package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
- package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
- package/scripts/hall-preview/fixtures/public__grades.json +0 -50
- package/scripts/hall-preview/fixtures/public__progress.json +0 -124
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
- package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
- package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
- package/scripts/hall-preview/fixtures/security__docs.json +0 -10
- package/scripts/hall-preview/fixtures/security__reports.json +0 -31
- package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
- package/scripts/hall-preview/fixtures/tasks.json +0 -2420
- package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
- package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
- package/scripts/hall-preview/fixtures/versions.json +0 -89
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Re-package electron-builder's mac DMGs so the install-window backdrop renders
|
|
3
|
+
# (task 1235). See scripts/dmgbuild-settings.py for the root cause: Finder on
|
|
4
|
+
# macOS >= Sonoma can't resolve the `pBBk` bookmark electron-builder writes, so
|
|
5
|
+
# its DMG shows a white window; dmgbuild's alias-only .DS_Store resolves and the
|
|
6
|
+
# backdrop (task 912) draws.
|
|
7
|
+
#
|
|
8
|
+
# For each dist/<ProductSlug>-<ver>-<arch>.dmg this:
|
|
9
|
+
# 1. mounts it read-only and copies the .app out with `ditto` (preserves the
|
|
10
|
+
# Developer ID signature + the stapled notarization ticket verbatim),
|
|
11
|
+
# 2. rebuilds the DMG with dmgbuild + the on-brand backdrop,
|
|
12
|
+
# 3. overwrites the original in place.
|
|
13
|
+
# The .app is byte-identical, so it stays notarized; the DMG is an unsigned
|
|
14
|
+
# carrier either way (verified: the published DMGs were never DMG-signed, only
|
|
15
|
+
# the app inside is). dmgbuild needs no GUI/Finder, so this is CI-safe (headless).
|
|
16
|
+
#
|
|
17
|
+
# Run from the devbox-app/ directory: bash scripts/apply-dmg-backdrop.sh [distDir]
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
DIST="${1:-dist}"
|
|
21
|
+
RES="build-resources"
|
|
22
|
+
WORK="$(mktemp -d)"
|
|
23
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
24
|
+
|
|
25
|
+
if [ ! -f "$RES/dmg-background.png" ] || [ ! -f "$RES/dmg-background@2x.png" ]; then
|
|
26
|
+
echo "ERROR: backdrop art missing in $RES (run scripts/gen-dmg-backdrop.py)" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Crisp on Retina: fold the 1x + 2x PNGs into a single multi-rep hidpi TIFF.
|
|
31
|
+
tiffutil -cathidpicheck "$RES/dmg-background.png" "$RES/dmg-background@2x.png" \
|
|
32
|
+
-out "$WORK/backdrop.tiff" >/dev/null
|
|
33
|
+
|
|
34
|
+
# Isolated, headless dmgbuild (writes .DS_Store directly; no Finder dependency).
|
|
35
|
+
python3 -m venv "$WORK/venv"
|
|
36
|
+
"$WORK/venv/bin/pip" install --quiet --disable-pip-version-check dmgbuild
|
|
37
|
+
|
|
38
|
+
# Brand comes from the generated brand (ADR 0145) so this re-packages whatever
|
|
39
|
+
# the branding-driven build produced — Cloud-Bongos-Dev-Box-*.dmg here, any
|
|
40
|
+
# instance's slug elsewhere — never a hardcoded product name.
|
|
41
|
+
SLUG="$(python3 -c 'import json;print(json.load(open("src/brand.generated.json"))["PRODUCT_SLUG"])')"
|
|
42
|
+
PNAME="$(python3 -c 'import json;print(json.load(open("src/brand.generated.json"))["PRODUCT_NAME"])')"
|
|
43
|
+
|
|
44
|
+
shopt -s nullglob
|
|
45
|
+
count=0
|
|
46
|
+
for DMG in "$DIST"/"$SLUG"-*.dmg; do
|
|
47
|
+
base="$(basename "$DMG" .dmg)" # e.g. Cloud-Bongos-Dev-Box-1.0.10-arm64
|
|
48
|
+
suffix="${base#"$SLUG"-}" # e.g. 1.0.10-arm64
|
|
49
|
+
volname="$PNAME $suffix" # e.g. Cloud Bongos Dev Box 1.0.10-arm64
|
|
50
|
+
|
|
51
|
+
mnt="$WORK/mnt-$suffix"
|
|
52
|
+
mkdir -p "$mnt"
|
|
53
|
+
hdiutil attach "$DMG" -nobrowse -readonly -mountpoint "$mnt" -owners off >/dev/null
|
|
54
|
+
|
|
55
|
+
app="$(/bin/ls -d "$mnt"/*.app 2>/dev/null | head -1)"
|
|
56
|
+
if [ -z "$app" ]; then
|
|
57
|
+
echo "ERROR: no .app inside $DMG" >&2
|
|
58
|
+
hdiutil detach "$mnt" >/dev/null 2>&1 || true
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
appname="$(basename "$app")"
|
|
62
|
+
ditto "$app" "$WORK/$appname"
|
|
63
|
+
hdiutil detach "$mnt" >/dev/null
|
|
64
|
+
|
|
65
|
+
out="$WORK/out-$suffix.dmg"
|
|
66
|
+
"$WORK/venv/bin/dmgbuild" \
|
|
67
|
+
-s scripts/dmgbuild-settings.py \
|
|
68
|
+
-D app="$WORK/$appname" \
|
|
69
|
+
-D bg="$WORK/backdrop.tiff" \
|
|
70
|
+
"$volname" "$out" >/dev/null
|
|
71
|
+
|
|
72
|
+
mv -f "$out" "$DMG"
|
|
73
|
+
rm -rf "$WORK/$appname"
|
|
74
|
+
echo " backdrop applied: $(basename "$DMG") (volume \"$volname\")"
|
|
75
|
+
count=$((count + 1))
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
if [ "$count" -eq 0 ]; then
|
|
79
|
+
echo "ERROR: no ${SLUG}-*.dmg found in $DIST" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
echo "Re-packaged $count DMG(s) with the install-window backdrop."
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# dmgbuild settings for the OTB Dev Box install window (task 1235).
|
|
2
|
+
#
|
|
3
|
+
# WHY dmgbuild and not electron-builder's own DMG styling:
|
|
4
|
+
# electron-builder writes a .DS_Store whose background reference includes a
|
|
5
|
+
# modern security-scoped bookmark (`pBBk`). Finder on macOS >= Sonoma/Sequoia
|
|
6
|
+
# (verified white on 15.7.4) cannot resolve that bookmark, so it falls back to a
|
|
7
|
+
# solid white window — the embedded Greek-coast backdrop (task 912) is present
|
|
8
|
+
# but never drawn. dmgbuild writes an alias-only `.DS_Store` (no `pBBk`) that
|
|
9
|
+
# Finder DOES resolve, so the backdrop renders. The OS is not the problem; the
|
|
10
|
+
# bookmark electron-builder emits is. scripts/apply-dmg-backdrop.sh re-packages
|
|
11
|
+
# electron-builder's signed+notarized .app into a dmgbuild DMG using this file.
|
|
12
|
+
#
|
|
13
|
+
# Coordinates MUST match the icon-well centers drawn by gen-dmg-backdrop.py
|
|
14
|
+
# (LEFT 180,198 / RIGHT 480,198) and the 660x400 art — change one, change all.
|
|
15
|
+
# `app` and `bg` are passed in via dmgbuild -D defines.
|
|
16
|
+
|
|
17
|
+
import os.path
|
|
18
|
+
|
|
19
|
+
# -D app=<path to OTB Dev Box.app> -D bg=<path to the hidpi backdrop tiff>
|
|
20
|
+
application = defines["app"]
|
|
21
|
+
backdrop = defines["bg"]
|
|
22
|
+
appname = os.path.basename(application) # "OTB Dev Box.app"
|
|
23
|
+
|
|
24
|
+
# Contents of the disk image.
|
|
25
|
+
files = [application]
|
|
26
|
+
symlinks = {"Applications": "/Applications"}
|
|
27
|
+
|
|
28
|
+
# Window + icon layout — matched to the backdrop art's two marble wells.
|
|
29
|
+
background = backdrop
|
|
30
|
+
window_rect = ((150, 150), (660, 400)) # ((x, y), (w, h)); w/h match the 660x400 art
|
|
31
|
+
icon_size = 100
|
|
32
|
+
text_size = 12
|
|
33
|
+
icon_locations = {
|
|
34
|
+
appname: (180, 198), # the container (app) — left well
|
|
35
|
+
"Applications": (480, 198), # Applications — right well
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Compressed, read-only final image (same distribution format as before).
|
|
39
|
+
format = "UDZO"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// modules/dev-box/app/scripts/gen-brand.js — make the Dev Box app branding-driven.
|
|
2
|
+
//
|
|
3
|
+
// The app is a self-contained Electron subproject (ADR 0045) built separately
|
|
4
|
+
// from the server, so it can't read the instance branding at runtime. Instead
|
|
5
|
+
// this reads the instance's config/branding.json (the SAME contract the server
|
|
6
|
+
// resolves — docs/branding-contract.md) at BUILD time and emits three generated
|
|
7
|
+
// artifacts the app + electron-builder consume:
|
|
8
|
+
//
|
|
9
|
+
// src/brand.generated.json — every user-visible string + appId/slug/colors
|
|
10
|
+
// renderer/theme.generated.css — the :root design tokens (palette + fonts)
|
|
11
|
+
// electron-builder.yml — rendered from electron-builder.tmpl.yml
|
|
12
|
+
//
|
|
13
|
+
// So ONE source tree builds a "Cloud Bongos Dev Box" here and an "Amazonprimea
|
|
14
|
+
// Dev Box" on the OTB instance — the brand is a build input, not baked into code
|
|
15
|
+
// (the ADR 0004 posture, generalised: no instance's name is hardcoded anywhere).
|
|
16
|
+
//
|
|
17
|
+
// Run: npm run gen-brand (also invoked before start / test / dist:*).
|
|
18
|
+
// Resolution: BONGOS_BRANDING_FILE env → <repoRoot>/config/branding.json →
|
|
19
|
+
// <repoRoot>/config/branding.neutral.json (the Cloud Bongos pack).
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
|
|
24
|
+
const APP_DIR = path.resolve(__dirname, '..'); // modules/dev-box/app
|
|
25
|
+
const REPO_ROOT = path.resolve(APP_DIR, '..', '..', '..'); // repo root (…/app → …/dev-box → …/modules → root)
|
|
26
|
+
|
|
27
|
+
function readBranding() {
|
|
28
|
+
const candidates = [
|
|
29
|
+
process.env.BONGOS_BRANDING_FILE,
|
|
30
|
+
path.join(REPO_ROOT, 'config', 'branding.json'),
|
|
31
|
+
path.join(REPO_ROOT, 'config', 'branding.neutral.json'),
|
|
32
|
+
].filter(Boolean);
|
|
33
|
+
for (const f of candidates) {
|
|
34
|
+
try {
|
|
35
|
+
const b = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
36
|
+
return { file: f, branding: b };
|
|
37
|
+
} catch { /* try next */ }
|
|
38
|
+
}
|
|
39
|
+
throw new Error('gen-brand: no branding file found (looked for config/branding.json, config/branding.neutral.json)');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// "Cloud Bongos" → "cloudbongos" (appId segment); "Cloud Bongos Dev Box" →
|
|
43
|
+
// "Cloud-Bongos-Dev-Box" (artifact filename — no spaces, ADR 0004).
|
|
44
|
+
const idSlug = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '') || 'instance';
|
|
45
|
+
const fileSlug = (s) => String(s || '').trim().replace(/[^A-Za-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'Dev-Box';
|
|
46
|
+
|
|
47
|
+
function buildBrand(branding) {
|
|
48
|
+
const id = branding.identity || {};
|
|
49
|
+
const dom = branding.domains || {};
|
|
50
|
+
const ui = (branding.theme && branding.theme.ui) || {};
|
|
51
|
+
const colors = ui.colors || {};
|
|
52
|
+
const world = id.worldName || id.productName || 'Cloud Bongos';
|
|
53
|
+
const company = (id.company && id.company.name) || world;
|
|
54
|
+
const productName = `${world} Dev Box`;
|
|
55
|
+
return {
|
|
56
|
+
// Rendered-at-runtime display strings (brand.js re-exports these).
|
|
57
|
+
CIV_NAME: world,
|
|
58
|
+
PRODUCT_NAME: productName,
|
|
59
|
+
DESKTOP_ENV_NAME: productName,
|
|
60
|
+
// Origins the built app pairs against. A release BUILD overrides these via
|
|
61
|
+
// env (DEVBOX_PUBLIC_ORIGIN/_BUILDERS_ORIGIN — set from CI repo variables)
|
|
62
|
+
// so the neutral core stays neutral (localhost) yet a cloudbongos.com build
|
|
63
|
+
// bakes cloudbongos.com — without committing a host origin to the core.
|
|
64
|
+
PUBLIC_ORIGIN: process.env.DEVBOX_PUBLIC_ORIGIN || dom.publicOrigin || 'http://localhost:3000',
|
|
65
|
+
BUILDERS_ORIGIN: process.env.DEVBOX_BUILDERS_ORIGIN || dom.buildersOrigin || dom.publicOrigin || 'http://localhost:3000',
|
|
66
|
+
// Build + packaging inputs.
|
|
67
|
+
PRODUCT_SLUG: fileSlug(productName),
|
|
68
|
+
APP_ID: `com.${idSlug(world)}.devbox`,
|
|
69
|
+
COPYRIGHT: `© ${company}`,
|
|
70
|
+
// Local integration: match the instance's CLI config dir so pairing the app
|
|
71
|
+
// also signs in the /builder-* CLI on this machine (session-store.js).
|
|
72
|
+
CONFIG_DIR: branding.configDir || 'cloudbongos',
|
|
73
|
+
// Panel header epigraph (branding copy.tagline).
|
|
74
|
+
TAGLINE: (branding.copy && branding.copy.tagline) || '',
|
|
75
|
+
// Pre-CSS window background (main.js) — the theme bg so first paint is on-brand.
|
|
76
|
+
BG_COLOR: colors.bg || '#ffffff',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The renderer :root tokens — mirror renderer/styles.css var names.
|
|
81
|
+
function buildThemeCss(branding) {
|
|
82
|
+
const ui = (branding.theme && branding.theme.ui) || {};
|
|
83
|
+
const c = ui.colors || {};
|
|
84
|
+
const fonts = ui.fonts || {};
|
|
85
|
+
const v = (x, fb) => x || fb;
|
|
86
|
+
return `/* GENERATED by scripts/gen-brand.js from config/branding.json — do not edit by hand. */
|
|
87
|
+
:root {
|
|
88
|
+
--bg: ${v(c.bg, '#fff7ed')};
|
|
89
|
+
--bg-card: ${v(c.bgCard, '#ffffff')};
|
|
90
|
+
--bg-deep: ${v(c.bgDeep, '#ffedd5')};
|
|
91
|
+
--ink: ${v(c.ink, '#2a1f17')};
|
|
92
|
+
--ink-soft: ${v(c.inkSoft, '#6b5d52')};
|
|
93
|
+
--ink-faint: ${v(c.inkFaint, '#857567')};
|
|
94
|
+
--rule: ${v(c.rule, '#fdba74')};
|
|
95
|
+
--rule-soft: ${v(c.ruleSoft, '#fed7aa')};
|
|
96
|
+
--accent: ${v(c.accent, '#f97316')};
|
|
97
|
+
--accent-deep: ${v(c.accentDeep, '#ea580c')};
|
|
98
|
+
--terracotta: ${v(c.terracotta, '#fb7185')};
|
|
99
|
+
--font-display: ${v(fonts.display, '"Baloo 2", "Quicksand", system-ui, sans-serif')};
|
|
100
|
+
--font-body: ${v(fonts.body, '"Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif')};
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderElectronBuilder(brand) {
|
|
106
|
+
const tmplPath = path.join(APP_DIR, 'electron-builder.tmpl.yml');
|
|
107
|
+
const tmpl = fs.readFileSync(tmplPath, 'utf8');
|
|
108
|
+
return tmpl
|
|
109
|
+
.replace(/@@APP_ID@@/g, brand.APP_ID)
|
|
110
|
+
.replace(/@@PRODUCT_NAME@@/g, brand.PRODUCT_NAME)
|
|
111
|
+
.replace(/@@COPYRIGHT@@/g, brand.COPYRIGHT)
|
|
112
|
+
.replace(/@@PRODUCT_SLUG@@/g, brand.PRODUCT_SLUG);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function main() {
|
|
116
|
+
const { file, branding } = readBranding();
|
|
117
|
+
const brand = buildBrand(branding);
|
|
118
|
+
|
|
119
|
+
const brandOut = path.join(APP_DIR, 'src', 'brand.generated.json');
|
|
120
|
+
fs.writeFileSync(brandOut, `${JSON.stringify(brand, null, 2)}\n`);
|
|
121
|
+
|
|
122
|
+
const themeOut = path.join(APP_DIR, 'renderer', 'theme.generated.css');
|
|
123
|
+
fs.writeFileSync(themeOut, buildThemeCss(branding));
|
|
124
|
+
|
|
125
|
+
const ebOut = path.join(APP_DIR, 'electron-builder.yml');
|
|
126
|
+
fs.writeFileSync(ebOut, renderElectronBuilder(brand));
|
|
127
|
+
|
|
128
|
+
const rel = (p) => path.relative(REPO_ROOT, p);
|
|
129
|
+
console.log(`gen-brand: ${brand.PRODUCT_NAME} (${brand.APP_ID}) ← ${rel(file)}`);
|
|
130
|
+
console.log(` wrote ${rel(brandOut)}, ${rel(themeOut)}, ${rel(ebOut)}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (require.main === module) main();
|
|
134
|
+
|
|
135
|
+
// Exported so a test can call the PURE builders (tests/devbox_installer_brand.mjs
|
|
136
|
+
// gates the committed artifacts against them). Importing must never write —
|
|
137
|
+
// hence the require.main guard above.
|
|
138
|
+
module.exports = { readBranding, buildBrand, buildThemeCss, renderElectronBuilder, main };
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Draw the DMG install-window backdrop from the INSTANCE BRANDING (task 912, 1002372).
|
|
3
|
+
|
|
4
|
+
The Mac .dmg install window shows two icons — the app on the left and an
|
|
5
|
+
Applications-folder alias on the right — and the user drags one onto the other.
|
|
6
|
+
This draws the Finder-window BACKDROP behind that gesture.
|
|
7
|
+
|
|
8
|
+
Branding is a BUILD INPUT, not baked in — the same posture gen-brand.js already
|
|
9
|
+
holds for the app's strings (ADR 0145). The palette comes from the pack's
|
|
10
|
+
`theme.ui.colors`, the headline names the product the build actually ships, the
|
|
11
|
+
subhead is the pack's tagline, and the type is the pack's own font stack. So ONE
|
|
12
|
+
source tree renders the vanilla Cloud Bongos sunrise here and a host instance's
|
|
13
|
+
palette + voice there: no hex, motif, or sentence belongs to a single brand.
|
|
14
|
+
|
|
15
|
+
ONE source, resolved exactly as gen-brand.js resolves it (docs/branding-contract.md):
|
|
16
|
+
BONGOS_BRANDING_FILE → config/branding.json → config/branding.neutral.json
|
|
17
|
+
Palette, type, product name and tagline all come from that file, so a render can
|
|
18
|
+
never mix one pack's colours with another's words.
|
|
19
|
+
|
|
20
|
+
A pack that wants different words states them outright as `copy.dmgHeadline` /
|
|
21
|
+
`copy.dmgSubhead`; otherwise both lines are derived, so a new instance needs no
|
|
22
|
+
extra config to get a correct install screen.
|
|
23
|
+
|
|
24
|
+
Draws DIRECTLY with Pillow (no SVG renderer needed in CI/headless) and
|
|
25
|
+
supersamples 2x then LANCZOS-downscales so text + edges read crisply at Retina.
|
|
26
|
+
Outputs:
|
|
27
|
+
build-resources/dmg-background.png 660x400 (@1x)
|
|
28
|
+
build-resources/dmg-background@2x.png 1320x800 (@2x; electron-builder auto-picks)
|
|
29
|
+
|
|
30
|
+
The icon SLOTS are left clear — electron-builder / dmgbuild place the real app +
|
|
31
|
+
Applications icons over them at the coords mirrored in scripts/dmgbuild-settings.py;
|
|
32
|
+
this art only draws the guidance AROUND them (wells, the beat line, copy).
|
|
33
|
+
|
|
34
|
+
Pillow is an ad-hoc dependency: the repo declares no Python manifest (the sibling
|
|
35
|
+
scripts/apply-dmg-backdrop.sh installs its own `dmgbuild` the same way), so run
|
|
36
|
+
this from a throwaway venv with `pip install Pillow`.
|
|
37
|
+
|
|
38
|
+
Run: python3 modules/dev-box/app/scripts/gen-dmg-backdrop.py
|
|
39
|
+
Re-run after any branding/layout/copy change; commit the two PNGs. Text renders in
|
|
40
|
+
the first face of the pack's stack that is INSTALLED on the rendering machine, so
|
|
41
|
+
a re-render on a different OS may substitute a different fallback face.
|
|
42
|
+
"""
|
|
43
|
+
from __future__ import annotations # `X | None` hints must not run on macOS's system python 3.9
|
|
44
|
+
|
|
45
|
+
import json
|
|
46
|
+
import os
|
|
47
|
+
from dataclasses import dataclass
|
|
48
|
+
from typing import Union
|
|
49
|
+
|
|
50
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
51
|
+
|
|
52
|
+
RGB = tuple[int, int, int]
|
|
53
|
+
Face = Union[ImageFont.FreeTypeFont, ImageFont.ImageFont] # a truetype face, or Pillow's built-in
|
|
54
|
+
|
|
55
|
+
APP_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # modules/dev-box/app
|
|
56
|
+
REPO_ROOT = os.path.abspath(os.path.join(APP_DIR, "..", "..", ".."))
|
|
57
|
+
|
|
58
|
+
W, H = 660, 400 # the DMG window size (must match electron-builder.yml dmg.window)
|
|
59
|
+
SS = 2 # supersample factor for crisp anti-aliased output
|
|
60
|
+
LEFT = (180, 198) # app-icon slot center (matches dmg.contents[0])
|
|
61
|
+
RIGHT = (480, 198) # Applications slot center (matches dmg.contents[1])
|
|
62
|
+
ICON = 100 # dmg.iconSize
|
|
63
|
+
|
|
64
|
+
# theme.ui.colors keys → the roles this art draws, with the neutral pack's values
|
|
65
|
+
# as fallbacks so a partial instance pack still renders something coherent.
|
|
66
|
+
# Every role here MUST be one the app's own brand surface emits (gen-brand.js's
|
|
67
|
+
# buildThemeCss) — a role outside it has nothing prompting a host pack to define
|
|
68
|
+
# it, so its "fallback" would paint a Cloud Bongos colour into a fully-specified
|
|
69
|
+
# host build. That is exactly what `sea` did to the Applications-well rim.
|
|
70
|
+
COLOR_ROLES = {
|
|
71
|
+
"bg": "#fff7ed", "bgDeep": "#ffedd5", "bgCard": "#ffffff",
|
|
72
|
+
"ink": "#2a1f17", "inkSoft": "#6b5d52", "ruleSoft": "#fed7aa",
|
|
73
|
+
"accent": "#f97316", "accentDeep": "#ea580c", "terracotta": "#fb7185",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
FONT_DIRS = (
|
|
77
|
+
"/System/Library/Fonts/Supplemental", "/Library/Fonts", "/System/Library/Fonts",
|
|
78
|
+
os.path.join(os.environ.get("WINDIR", "C:/Windows"), "Fonts"),
|
|
79
|
+
"/usr/share/fonts/truetype/dejavu",
|
|
80
|
+
)
|
|
81
|
+
# Tried only when none of the pack's own families is installed — a web-font stack
|
|
82
|
+
# ("Baloo 2", "Quicksand") usually isn't present on the machine doing the render.
|
|
83
|
+
GENERIC_BOLD = ("Arial Bold.ttf", "Helvetica.ttc", "segoeuib.ttf", "arialbd.ttf", "DejaVuSans-Bold.ttf")
|
|
84
|
+
GENERIC_REGULAR = ("Arial.ttf", "Helvetica.ttc", "segoeui.ttf", "arial.ttf", "DejaVuSans.ttf")
|
|
85
|
+
# CSS keywords name no file, so they fall through to the generic faces above.
|
|
86
|
+
CSS_GENERIC_FAMILIES = {"system-ui", "-apple-system", "sans-serif", "serif", "monospace", "ui-sans-serif"}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class Art:
|
|
91
|
+
"""Everything the render pulls from the instance pack — resolved once, then drawn."""
|
|
92
|
+
palette: dict[str, RGB]
|
|
93
|
+
headline: str
|
|
94
|
+
subhead: str
|
|
95
|
+
display_stack: str
|
|
96
|
+
body_stack: str
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def read_json(path: str) -> dict | None:
|
|
100
|
+
try:
|
|
101
|
+
with open(path, encoding="utf-8") as fh:
|
|
102
|
+
return json.load(fh)
|
|
103
|
+
except (OSError, ValueError):
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def read_branding() -> dict:
|
|
108
|
+
"""The pack, resolved exactly as gen-brand.js resolves it (env → instance → neutral)."""
|
|
109
|
+
for path in (os.environ.get("BONGOS_BRANDING_FILE"),
|
|
110
|
+
os.path.join(REPO_ROOT, "config", "branding.json"),
|
|
111
|
+
os.path.join(REPO_ROOT, "config", "branding.neutral.json")):
|
|
112
|
+
pack = read_json(path) if path else None
|
|
113
|
+
if pack is not None:
|
|
114
|
+
return pack
|
|
115
|
+
raise SystemExit("gen-dmg-backdrop: no branding pack found (is config/branding.neutral.json missing?)")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def brand_strings(branding: dict) -> dict:
|
|
119
|
+
"""Product name + tagline, derived from the resolved pack exactly as gen-brand.js's
|
|
120
|
+
buildBrand derives them (`<worldName> Dev Box`, `copy.tagline`).
|
|
121
|
+
|
|
122
|
+
Deliberately NOT read out of gen-brand.js's src/brand.generated.json, which carries
|
|
123
|
+
the same two fields: that artifact is COMMITTED holding the neutral values and
|
|
124
|
+
nothing on this path regenerates it, so preferring it would let a stale checked-in
|
|
125
|
+
file outrank the host's own pack — the render would draw the host palette and type
|
|
126
|
+
under the neutral product's name. The pack decides, every time."""
|
|
127
|
+
identity = branding.get("identity") or {}
|
|
128
|
+
world = identity.get("worldName") or identity.get("productName") or "Cloud Bongos"
|
|
129
|
+
return {
|
|
130
|
+
"product": f"{world} Dev Box",
|
|
131
|
+
"tagline": (branding.get("copy") or {}).get("tagline") or "",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def copy_lines(branding: dict, brand: dict) -> tuple[str, str]:
|
|
136
|
+
"""Headline + subhead. Derived from the instance's own name and tagline; a pack
|
|
137
|
+
whose voice needs different words states them as copy.dmgHeadline/dmgSubhead."""
|
|
138
|
+
copy = branding.get("copy") or {}
|
|
139
|
+
headline = copy.get("dmgHeadline") or f"Drag {brand['product']} to Applications"
|
|
140
|
+
subhead = copy.get("dmgSubhead")
|
|
141
|
+
return headline, brand["tagline"] if subhead is None else subhead
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def hexrgb(value: str | None, fallback: str) -> RGB:
|
|
145
|
+
"""A pack colour, or the neutral fallback when the pack omits it or writes junk —
|
|
146
|
+
one malformed hex must not abort a build over a decorative backdrop."""
|
|
147
|
+
for candidate in (value, fallback):
|
|
148
|
+
raw = str(candidate or "").lstrip("#")
|
|
149
|
+
if len(raw) == 3:
|
|
150
|
+
raw = "".join(c * 2 for c in raw)
|
|
151
|
+
if len(raw) == 6:
|
|
152
|
+
try:
|
|
153
|
+
return (int(raw[0:2], 16), int(raw[2:4], 16), int(raw[4:6], 16))
|
|
154
|
+
except ValueError:
|
|
155
|
+
continue
|
|
156
|
+
return (0, 0, 0)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def palette(branding: dict) -> dict[str, RGB]:
|
|
160
|
+
ui = (branding.get("theme") or {}).get("ui") or {}
|
|
161
|
+
colors = ui.get("colors") or {}
|
|
162
|
+
return {role: hexrgb(colors.get(role), fallback) for role, fallback in COLOR_ROLES.items()}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def font_files(stack: str, bold: bool) -> list[str]:
|
|
166
|
+
"""A CSS family stack → font FILENAMES to try, in the pack's own preference order.
|
|
167
|
+
Covers both the macOS ("Georgia Bold.ttf") and Windows ("georgiab.ttf") naming."""
|
|
168
|
+
names: list[str] = []
|
|
169
|
+
for raw in str(stack or "").split(","):
|
|
170
|
+
family = raw.strip().strip("\"'")
|
|
171
|
+
if not family or family in CSS_GENERIC_FAMILIES:
|
|
172
|
+
continue
|
|
173
|
+
flat = family.replace(" ", "").lower()
|
|
174
|
+
if bold:
|
|
175
|
+
names += [f"{family} Bold.ttf", f"{flat}b.ttf", f"{flat}bd.ttf"]
|
|
176
|
+
names += [f"{family}.ttf", f"{flat}.ttf"]
|
|
177
|
+
return names + list(GENERIC_BOLD if bold else GENERIC_REGULAR)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def font(stack: str, size: int, bold: bool = False) -> Face:
|
|
181
|
+
for name in font_files(stack, bold):
|
|
182
|
+
for directory in FONT_DIRS:
|
|
183
|
+
path = os.path.join(directory, name)
|
|
184
|
+
if os.path.exists(path):
|
|
185
|
+
return ImageFont.truetype(path, size)
|
|
186
|
+
return ImageFont.load_default(size)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def lerp(a: RGB, b: RGB, t: float) -> RGB:
|
|
190
|
+
return tuple(round(a[i] + (b[i] - a[i]) * t) for i in range(3))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def vgradient(draw: ImageDraw.ImageDraw, x0: int, y0: int, x1: int, y1: int, c_top: RGB, c_bot: RGB) -> None:
|
|
194
|
+
h = max(1, y1 - y0)
|
|
195
|
+
for y in range(y0, y1):
|
|
196
|
+
draw.line([(x0, y), (x1, y)], fill=lerp(c_top, c_bot, (y - y0) / h))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def text_width(draw: ImageDraw.ImageDraw, text: str, fnt: Face) -> float:
|
|
200
|
+
left, _, right, _ = draw.textbbox((0, 0), text, font=fnt)
|
|
201
|
+
return right - left
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def centered(draw: ImageDraw.ImageDraw, cx: float, y: float, text: str,
|
|
205
|
+
fnt: Face, fill: RGB) -> None:
|
|
206
|
+
draw.text((cx - text_width(draw, text, fnt) / 2, y), text, font=fnt, fill=fill)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def fitted_font(draw: ImageDraw.ImageDraw, text: str, stack: str, size: int, max_w: float) -> Face:
|
|
210
|
+
"""Shrink the headline until it fits — an instance's product name is arbitrary
|
|
211
|
+
length, and a name that runs off the 660px art would ship a broken installer."""
|
|
212
|
+
fnt = font(stack, size, bold=True)
|
|
213
|
+
while size > 12 and text_width(draw, text, fnt) > max_w:
|
|
214
|
+
size -= 1
|
|
215
|
+
fnt = font(stack, size, bold=True)
|
|
216
|
+
return fnt
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def cloud_band(draw: ImageDraw.ImageDraw, base_y: float, scale: int, fill: RGB) -> None:
|
|
220
|
+
# A soft cloud shelf across the foot: a flat fill topped by overlapping discs,
|
|
221
|
+
# so the band reads as one puffy silhouette and grounds the two icon wells.
|
|
222
|
+
y = base_y * scale
|
|
223
|
+
draw.rectangle([0, y, W * scale, H * scale], fill=fill)
|
|
224
|
+
x = -12.0 * scale
|
|
225
|
+
i = 0
|
|
226
|
+
while x < (W + 40) * scale:
|
|
227
|
+
r = (11 + 5 * ((i * 3) % 4)) * scale
|
|
228
|
+
draw.ellipse([x - r, y - r, x + r, y + r], fill=fill)
|
|
229
|
+
x += r * 1.15
|
|
230
|
+
i += 1
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def bongo_mark(draw: ImageDraw.ImageDraw, center: tuple[float, float], scale: int,
|
|
234
|
+
big: RGB, small: RGB, head: RGB) -> None:
|
|
235
|
+
# The mark: a drum pair seen from above, mirroring the neutral pack's favicon
|
|
236
|
+
# geometry but coloured FROM the resolved pack — so it recolours per instance
|
|
237
|
+
# rather than stamping one brand's emblem into every build.
|
|
238
|
+
cx, cy = center[0] * scale, center[1] * scale
|
|
239
|
+
for dx, radius, body in ((-8.0, 13.0, big), (9.0, 10.0, small)):
|
|
240
|
+
x = cx + dx * scale
|
|
241
|
+
r = radius * scale
|
|
242
|
+
draw.ellipse([x - r, cy - r, x + r, cy + r], fill=body)
|
|
243
|
+
inner = r * 0.44
|
|
244
|
+
draw.ellipse([x - inner, cy - inner, x + inner, cy + inner], fill=head)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def well(draw: ImageDraw.ImageDraw, center: tuple[int, int], scale: int, face: RGB, rim: RGB) -> None:
|
|
248
|
+
# A soft raised disc the icon sits on — a filled card with concentric rings
|
|
249
|
+
# fading into it, so the placed icon reads as resting ON the surface.
|
|
250
|
+
cx, cy = center[0] * scale, center[1] * scale
|
|
251
|
+
rad = (ICON / 2 + 18) * scale
|
|
252
|
+
draw.ellipse([cx - rad, cy - rad, cx + rad, cy + rad], fill=face)
|
|
253
|
+
for i in range(6):
|
|
254
|
+
r = rad - i * 1.6 * scale
|
|
255
|
+
draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=lerp(rim, face, i / 5),
|
|
256
|
+
width=max(1, int(1.5 * scale)))
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def beat_line(draw: ImageDraw.ImageDraw, p0: tuple[float, float], p1: tuple[float, float],
|
|
260
|
+
scale: int, dot: RGB, tip: RGB) -> None:
|
|
261
|
+
# The drag guidance: beats swelling toward a chevron. Direction without a
|
|
262
|
+
# world-specific motif — it reads the same whatever the instance is building.
|
|
263
|
+
x0, y0 = p0[0] * scale, p0[1] * scale
|
|
264
|
+
x1, y1 = p1[0] * scale, p1[1] * scale
|
|
265
|
+
beats = 6
|
|
266
|
+
for i in range(beats):
|
|
267
|
+
t = i / (beats - 1)
|
|
268
|
+
x, y = x0 + (x1 - x0) * t, y0 + (y1 - y0) * t
|
|
269
|
+
r = (2.0 + 2.4 * t) * scale
|
|
270
|
+
draw.ellipse([x - r, y - r, x + r, y + r], fill=dot)
|
|
271
|
+
a = 12 * scale
|
|
272
|
+
draw.polygon([(x1 + a * 1.5, y1), (x1 + a * 0.2, y1 - a * 0.8), (x1 + a * 0.2, y1 + a * 0.8)], fill=tip)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def render(scale: int, art: Art) -> Image.Image:
|
|
276
|
+
p = art.palette
|
|
277
|
+
img = Image.new("RGB", (W * scale, H * scale), p["bg"])
|
|
278
|
+
d = ImageDraw.Draw(img)
|
|
279
|
+
# the pack's field: its deep tint at the top easing into its base tone
|
|
280
|
+
vgradient(d, 0, 0, W * scale, H * scale, p["bgDeep"], p["bg"])
|
|
281
|
+
cloud_band(d, 344, scale, p["bgCard"])
|
|
282
|
+
bongo_mark(d, (W / 2, 34), scale, p["accent"], p["terracotta"], p["bg"])
|
|
283
|
+
centered(d, W * scale / 2, int(56 * scale), art.headline,
|
|
284
|
+
fitted_font(d, art.headline, art.display_stack, int(25 * scale), (W - 72) * scale), p["ink"])
|
|
285
|
+
if art.subhead:
|
|
286
|
+
centered(d, W * scale / 2, int(94 * scale), art.subhead,
|
|
287
|
+
font(art.body_stack, int(13 * scale)), p["inkSoft"])
|
|
288
|
+
# Wells + guidance. The destination rim takes the pack's accent — the same tone
|
|
289
|
+
# the beats arrive in — so the drop target is distinguishable from the source's
|
|
290
|
+
# soft rule at a glance.
|
|
291
|
+
well(d, LEFT, scale, p["bgCard"], p["ruleSoft"])
|
|
292
|
+
well(d, RIGHT, scale, p["bgCard"], p["accent"])
|
|
293
|
+
beat_line(d, (LEFT[0] + ICON / 2 + 24, LEFT[1]), (RIGHT[0] - ICON / 2 - 36, RIGHT[1]),
|
|
294
|
+
scale, p["accent"], p["accentDeep"])
|
|
295
|
+
# NB: no under-slot labels — Finder renders each icon's own name beneath it,
|
|
296
|
+
# so drawing our own would double them.
|
|
297
|
+
return img
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def main() -> None:
|
|
301
|
+
branding = read_branding()
|
|
302
|
+
brand = brand_strings(branding)
|
|
303
|
+
headline, subhead = copy_lines(branding, brand)
|
|
304
|
+
fonts = ((branding.get("theme") or {}).get("ui") or {}).get("fonts") or {}
|
|
305
|
+
art = Art(
|
|
306
|
+
palette=palette(branding),
|
|
307
|
+
headline=headline,
|
|
308
|
+
subhead=subhead,
|
|
309
|
+
display_stack=fonts.get("display") or "",
|
|
310
|
+
body_stack=fonts.get("body") or "",
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
out_dir = os.path.join(APP_DIR, "build-resources")
|
|
314
|
+
# @1x: render at SS then downscale for anti-aliasing.
|
|
315
|
+
render(SS, art).resize((W, H), Image.LANCZOS).save(os.path.join(out_dir, "dmg-background.png"))
|
|
316
|
+
# @2x: native double-size canvas (so it is genuinely 2x detail), supersampled.
|
|
317
|
+
render(SS * 2, art).resize((W * 2, H * 2), Image.LANCZOS).save(os.path.join(out_dir, "dmg-background@2x.png"))
|
|
318
|
+
print(f'gen-dmg-backdrop: "{headline}" / "{subhead}"')
|
|
319
|
+
print(f" wrote {out_dir}/dmg-background.png ({W}x{H}) + dmg-background@2x.png ({W * 2}x{H * 2})")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
main()
|