@desplega.ai/agent-swarm 2.7.5 → 2.7.7
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/dist/{acp-adapter-g3ads55s.js → acp-adapter-7b4vk3h5.js} +5 -4
- package/dist/{actions-zwfykqqk.js → actions-3tt153af.js} +18 -17
- package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-njpts0yr.js} +1 -1
- package/dist/{anthropic-messages-x4q6rfq5.js → anthropic-messages-xey2skwx.js} +7 -7
- package/dist/{app-vmp4fgep.js → app-nky4378f.js} +3 -3
- package/dist/{assistant-vwde653f.js → assistant-jnb669pa.js} +24 -22
- package/dist/{azure-openai-responses-s1w5n70z.js → azure-openai-responses-26t7j9ra.js} +5 -5
- package/dist/{boot-reembed-375t7tbk.js → boot-reembed-9mgvaztt.js} +1 -1
- package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-qs2djdej.js} +1 -1
- package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-yjpdj7s1.js} +3 -3
- package/dist/{boot-seeds-aykx51v3.js → boot-seeds-5cm6ambd.js} +4 -4
- package/dist/{boxd-k1j26g3h.js → boxd-nv507va8.js} +5 -5
- package/dist/{claude-adapter-8j4hm099.js → claude-adapter-fn287zmm.js} +5 -4
- package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-wtdz2mj2.js} +1 -1
- package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-x91kqyhm.js} +9677 -9514
- package/dist/{cli-j3jvnpe8.js → cli-08bmz9z9.js} +1 -1
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-y24sc29k.js → cli-1c6tzt8g.js} +30 -8
- package/dist/{cli-mjzynrse.js → cli-35rsp8ye.js} +1 -1
- package/dist/{cli-rpm2sgv0.js → cli-3fg0bdcv.js} +19 -2
- package/dist/{cli-nn4d8dne.js → cli-5b456bpf.js} +2 -2
- package/dist/{cli-bhjt02p8.js → cli-5jnhv72d.js} +1 -1
- package/dist/{cli-fqzx7gwa.js → cli-5zx699j9.js} +2 -2
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-vqypxzh4.js → cli-6g0hb4cp.js} +8 -8
- package/dist/{cli-tqgg40ek.js → cli-6pfd54vx.js} +9 -8
- package/dist/{cli-bsn8nhrq.js → cli-6vcp76qk.js} +3 -3
- package/dist/{cli-zppwg9p9.js → cli-72v799xn.js} +1 -1
- package/dist/cli-82j36f2m.js +45 -0
- package/dist/{cli-hn9hxmz9.js → cli-8305gbq0.js} +29 -16
- package/dist/{cli-z8eh3ex8.js → cli-8sckt74b.js} +8 -8
- package/dist/{cli-bnz7a7qf.js → cli-95jzxcp3.js} +1 -1
- package/dist/{cli-0bxqnvq9.js → cli-adeegh7d.js} +29 -7
- package/dist/{cli-g61qxmge.js → cli-afkqkrtw.js} +3 -3
- package/dist/{cli-7a42nfy0.js → cli-ayayvfg4.js} +24 -4
- package/dist/{cli-bqt1mptt.js → cli-az48n0nr.js} +4 -4
- package/dist/{cli-z6jkx23q.js → cli-bfv7vdg6.js} +9 -9
- package/dist/{cli-pywf3eh5.js → cli-caj33vcq.js} +5 -5
- package/dist/{cli-5pag6rmn.js → cli-cb2a8pna.js} +1 -1
- package/dist/{cli-wvaawacd.js → cli-cppkf404.js} +5236 -72
- package/dist/{cli-mry0qzym.js → cli-ddaanqkd.js} +123 -10
- package/dist/{cli-0gey5fzy.js → cli-e0kmphd6.js} +2 -2
- package/dist/{cli-zh3fp8v2.js → cli-e46hg0xw.js} +1 -1
- package/dist/{cli-jm2vfpfd.js → cli-e5bzrn01.js} +1 -1
- package/dist/{cli-qxnf8z2z.js → cli-excnnjg2.js} +165 -153
- package/dist/{cli-71ptav7x.js → cli-ferkkrg9.js} +1 -1
- package/dist/{cli-vpf89f34.js → cli-fmjd0t1y.js} +3 -3
- package/dist/{cli-rm6hqj3z.js → cli-fyt2g4wd.js} +33 -139
- package/dist/cli-g08zdaec.js +55 -0
- package/dist/{cli-ehq736cn.js → cli-g7s47cc5.js} +4 -4
- package/dist/{cli-82ae98pz.js → cli-gz6q4eve.js} +3 -3
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-rqkxrmgw.js → cli-je518txm.js} +39 -14
- package/dist/{cli-8hs41zy2.js → cli-jtjtabxe.js} +1 -1
- package/dist/{cli-611a1294.js → cli-k0wwfqbg.js} +18 -1
- package/dist/{cli-nzxr556s.js → cli-k1nje3v3.js} +38 -43
- package/dist/{cli-3h4nhjn8.js → cli-k8196sh2.js} +9 -7
- package/dist/{cli-afrrppsw.js → cli-k899ehvh.js} +2 -2
- package/dist/{cli-0731hvbw.js → cli-m0da8j35.js} +23 -9
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-8veytvbh.js → cli-mkk9rnzz.js} +5 -5
- package/dist/{cli-7wspm5gm.js → cli-n6bv12jn.js} +528 -6
- package/dist/{cli-spknvxbq.js → cli-nmzhp1b5.js} +14 -12
- package/dist/{cli-p599f1qm.js → cli-p82cnnjq.js} +1 -1
- package/dist/{cli-xtjhfmpc.js → cli-pc6n7fre.js} +1 -1
- package/dist/{cli-vbyfwh92.js → cli-pjctyea3.js} +2 -2
- package/dist/{cli-nmbmy64z.js → cli-pq73h5mz.js} +3 -3
- package/dist/{cli-aa6yxc7m.js → cli-pz4rk021.js} +1 -1
- package/dist/{cli-7wcyk2s5.js → cli-q3tkegby.js} +1 -1
- package/dist/cli-qghe0h66.js +67 -0
- package/dist/{cli-6s7vdcta.js → cli-qh4d4tvk.js} +4 -4
- package/dist/{cli-mmwm7zbf.js → cli-rtt8g4v9.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1h3wcpbw.js → cli-snpj5zbj.js} +2 -2
- package/dist/{cli-a7v854rf.js → cli-tgrv9cjx.js} +263 -12
- package/dist/{cli-a47xy8eq.js → cli-vz22fyqv.js} +186 -54
- package/dist/{cli-1ayk1eas.js → cli-wk5yrfwd.js} +285 -42
- package/dist/{cli-1338796d.js → cli-ws19768n.js} +1755 -119
- package/dist/{cli-70r77n04.js → cli-xae4g51r.js} +1 -1
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-xbkdm710.js → cli-yndmfxb3.js} +3 -3
- package/dist/{cli-rhw40jgb.js → cli-yyj3j5m4.js} +2 -2
- package/dist/cli.js +26 -25
- package/dist/{cloudflare-workers-ai-system-one-bm60fjm0.js → cloudflare-workers-ai-system-one-ef2a980q.js} +2 -2
- package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-cf2yxe9m.js} +3 -2
- package/dist/{codex-hook-9efm2x1m.js → codex-hook-a0c6g6t6.js} +1 -1
- package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-6vfzdgbd.js} +3 -2
- package/dist/{commands-evrenjn9.js → commands-t3mfy7vn.js} +9 -3
- package/dist/{controller-xjp8vswh.js → controller-q7xrbaz0.js} +8 -3
- package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-c6nm70kg.js} +20 -19
- package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-ftd81ga8.js} +1 -1
- package/dist/{db-zxpw6aht.js → db-dk572x25.js} +3 -3
- package/dist/{db-rgfsh91f.js → db-vemtpmcc.js} +14 -4
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-smasgws7.js → dispatcher-kh7ecxh9.js} +17 -16
- package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
- package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-v8628r89.js} +1 -1
- package/dist/{e2b-w1p0bnpm.js → e2b-fr12yccx.js} +5 -5
- package/dist/{e2b-qcaj675y.js → e2b-x6ywzak3.js} +3 -3
- package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-nenw4x63.js} +1 -1
- package/dist/{embedding-space-1z0mbg95.js → embedding-space-0jepb239.js} +6 -6
- package/dist/{embeddings-ygeg7tm7.js → embeddings-xxt49yxg.js} +1 -1
- package/dist/{execute-14qhqp4d.js → execute-1mhw6e4x.js} +1 -1
- package/dist/{external-channel-ttgrs3sx.js → external-channel-55v12e60.js} +2 -2
- package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-bs8wqrk5.js} +6 -5
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-v94hr7h0.js} +5 -5
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-g7vr6cf8.js} +5 -5
- package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-gpjqmkcq.js} +6 -5
- package/dist/handlers-zxmp1ta4.js +32 -0
- package/dist/{heartbeat-qahz4p62.js → heartbeat-k42svnr0.js} +20 -19
- package/dist/{hook-4fd5wq5w.js → hook-114m7hap.js} +4 -3
- package/dist/{hook-kj3d398m.js → hook-6nphweh0.js} +24 -9
- package/dist/{http-1ted0j1k.js → http-r8cc4wz7.js} +1196 -211
- package/dist/index-a8phxqnk.js +37 -0
- package/dist/{index-q4b917zs.js → index-e3s8wmxy.js} +1 -1
- package/dist/{index-6rq3r5zb.js → index-fpk4bcd9.js} +1 -1
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k6h00t4c.js +30 -0
- package/dist/{index-7v51awbn.js → index-nh3wyqdt.js} +1027 -577
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/index-xrz7te8h.js +33 -0
- package/dist/{keepalive-6cf311t5.js → keepalive-x060nt9p.js} +3 -3
- package/dist/lead-956sd2yc.js +71 -0
- package/dist/{maintenance-a4bm1bmt.js → maintenance-2xxfexhf.js} +1 -1
- package/dist/{mistral-conversations-4pmwj1c0.js → mistral-conversations-3gjpdb4b.js} +4 -4
- package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-fckc4ar5.js} +2 -2
- package/dist/{onboard-myn5hraa.js → onboard-zc48j237.js} +5 -5
- package/dist/{openai-codex-responses-44j573nm.js → openai-codex-responses-8pbxq9h5.js} +5 -5
- package/dist/{openai-completions-a4gee2a8.js → openai-completions-rm00s912.js} +4 -4
- package/dist/{openai-decisions-w4qkrcar.js → openai-decisions-8v957r2r.js} +1 -1
- package/dist/{openai-responses-rbp6h5kr.js → openai-responses-agxpf96k.js} +5 -5
- package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-r690h1cr.js} +4 -3
- package/dist/{otel-impl-a1a0ef6b.js → otel-impl-r5xbh81q.js} +4 -4
- package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-c7v8sdec.js} +7 -6
- package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-3pmre11v.js} +3 -3
- package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-3vbshwsb.js} +1 -1
- package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-910mmkgz.js} +2 -2
- package/dist/{render-v2-twmbgrz4.js → render-v2-pxykkbab.js} +3 -3
- package/dist/{resume-zfqc36xj.js → resume-adda0bwy.js} +6 -6
- package/dist/{runtime-qwggdpqr.js → runtime-fnbm7stk.js} +1 -1
- package/dist/{session-access-sc9pn9da.js → session-access-2fvamrdc.js} +4 -4
- package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-08xvv0sm.js} +2 -2
- package/dist/{setup-4fr0m7a1.js → setup-1aqmvmh1.js} +3 -3
- package/dist/{typesafe-system-one-5mavm259.js → typesafe-system-one-0bmqcxfg.js} +2 -2
- package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-d2pjk9j1.js} +5 -5
- package/dist/{work-objects-0wr8zxct.js → work-objects-9s0yrm5j.js} +20 -19
- package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-3q78fqmr.js} +17 -16
- package/dist/worker-y15hy08h.js +71 -0
- package/openapi.json +289 -4
- package/package.json +9 -8
- package/src/apps/definition.ts +6 -5
- package/src/be/db/page-theme-tokens.ts +12 -0
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/tasks/read.ts +131 -4
- package/src/be/db/tasks/write.ts +19 -0
- package/src/be/db-client.ts +114 -11
- package/src/be/db.ts +49 -6
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
- package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
- package/src/be/migrations/263_task_list_filter_index.sql +11 -0
- package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
- package/src/be/migrations/266_script_runs_source_task.sql +11 -0
- package/src/be/migrations/269_pages_design_system.sql +23 -0
- package/src/be/runtime-pools/cache-cleanup.ts +109 -0
- package/src/be/runtime-pools/db.ts +65 -6
- package/src/be/runtime-pools/remote-controller.ts +5 -0
- package/src/be/runtime-pools/supervisor.ts +33 -2
- package/src/be/scripts/typecheck.ts +22 -0
- package/src/be/seed/schedules-seeder.ts +0 -10
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +2 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +2 -0
- package/src/be/seed-scripts/catalog/memory-eval.ts +2 -0
- package/src/be/seed-scripts/catalog/swarm-update-check.ts +150 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/be/seed-scripts/index.ts +9 -0
- package/src/be/seed-skills/bundled-files.generated.json +3 -3
- package/src/be/swarm-config-guard.ts +32 -0
- package/src/be/task-auto-title-listener.ts +127 -0
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/commands/onboard/agent-fs-version.ts +1 -1
- package/src/commands/profile-sync.ts +151 -16
- package/src/commands/runner.ts +15 -7
- package/src/hooks/hook.ts +20 -9
- package/src/http/index.ts +4 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/memory.ts +38 -9
- package/src/http/pages-public.ts +93 -129
- package/src/http/pages.ts +94 -21
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/pages/browser-sdk.ts +48 -21
- package/src/pages/color-literals.ts +115 -0
- package/src/pages/page-head.ts +766 -0
- package/src/pages/version.ts +3 -0
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +1 -0
- package/src/providers/codex-app-server.ts +27 -1
- package/src/providers/fx-adapter.ts +1 -1
- package/src/providers/pi-mono-extension.ts +30 -15
- package/src/runtime-providers/controller-protocol.ts +14 -1
- package/src/runtime-providers/controller.ts +5 -0
- package/src/runtime-providers/docker.ts +108 -23
- package/src/runtime-providers/types.ts +16 -0
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/types/stdlib.d.ts +22 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
- package/src/server-user.ts +1 -0
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +37 -2
- package/src/slack/left-threads.ts +27 -4
- package/src/tasks/follow-up-gate.ts +318 -0
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tasks/worker-follow-up.ts +21 -1
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/codex-app-server.test.ts +59 -0
- package/src/tests/codex-gateway-route.test.ts +50 -1
- package/src/tests/create-page-tool.test.ts +501 -0
- package/src/tests/db-client-busy-retry.test.ts +81 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +9 -2
- package/src/tests/db-task-write-contract.test.ts +7 -1
- package/src/tests/db-tasks-read.test.ts +21 -0
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +14 -8
- package/src/tests/fixtures/pages/head-v1.html +56 -0
- package/src/tests/follow-up-gate-shadow.test.ts +513 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/identity-field-budget.test.ts +1 -1
- package/src/tests/internal-ai/credentials.test.ts +62 -0
- package/src/tests/internal-ai/task-title.test.ts +283 -0
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/merge-gate-detect-changes.test.ts +145 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-session-dev-cookie.test.ts +188 -0
- package/src/tests/pages-frame-ancestors.test.ts +28 -1
- package/src/tests/pages-head-v2.test.ts +534 -0
- package/src/tests/pages-http.test.ts +330 -0
- package/src/tests/pages-public-html.test.ts +43 -0
- package/src/tests/pages-storage.test.ts +182 -0
- package/src/tests/pages-versioning.test.ts +72 -0
- package/src/tests/profile-sync-stale-writeback.test.ts +334 -0
- package/src/tests/profile-sync.test.ts +11 -15
- package/src/tests/query-audit-plans.test.ts +27 -0
- package/src/tests/runtime-cache-cleanup.test.ts +367 -0
- package/src/tests/runtime-e2b-pools.test.ts +41 -0
- package/src/tests/runtime-pools-db.test.ts +98 -0
- package/src/tests/runtime-pools-http.test.ts +29 -0
- package/src/tests/runtime-providers-docker.test.ts +253 -0
- package/src/tests/runtime-remote-controller.test.ts +66 -2
- package/src/tests/runtime-supervisor.test.ts +66 -2
- package/src/tests/scheduled-task-required-params-migration.test.ts +3 -1
- package/src/tests/script-connections.test.ts +13 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-registry.test.ts +28 -6
- package/src/tests/seed-schedules.test.ts +61 -8
- package/src/tests/seed-scripts.test.ts +168 -2
- package/src/tests/slack-leave-thread.test.ts +145 -5
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/swarm-diff.test.ts +121 -2
- package/src/tests/task-auto-title-config-guard.test.ts +37 -0
- package/src/tests/task-auto-title-listener.test.ts +165 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tests/theme-import.test.ts +758 -0
- package/src/tests/themes-page-theme.test.ts +155 -0
- package/src/tests/themes-presets.test.ts +172 -0
- package/src/tests/workflow-script-principal.test.ts +2 -1
- package/src/themes/classics.ts +949 -0
- package/src/themes/import.ts +420 -0
- package/src/themes/page-fields.ts +40 -0
- package/src/themes/page-theme.ts +102 -0
- package/src/themes/presets.ts +561 -0
- package/src/themes/tokens.ts +218 -0
- package/src/tools/app-upsert.ts +1 -1
- package/src/tools/create-page.ts +118 -12
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +1 -1
- package/src/tools/task-dedup.ts +45 -21
- package/src/tools/utils.ts +33 -0
- package/src/types.ts +83 -3
- package/src/utils/constants.ts +19 -0
- package/src/utils/follow-up-gate-schedule-ids.ts +13 -0
- package/src/utils/internal-ai/credentials.ts +146 -93
- package/src/utils/internal-ai/index.ts +10 -0
- package/src/utils/internal-ai/models.ts +18 -0
- package/src/utils/internal-ai/task-title.ts +282 -0
- package/src/utils/page-session.ts +37 -0
- package/templates/schedules/daily-status-report/content.md +88 -2
- package/templates/schedules/daily-swarm-update-check/content.md +16 -40
- package/templates/skills/apps/SKILL.md +1 -1
- package/templates/skills/apps/content.md +1 -1
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/pages/config.json +1 -1
- package/templates/skills/pages/content.md +539 -86
- package/templates/skills/pages/files/examples/annotated-pr.html +21 -12
- package/templates/skills/pages/files/examples/multiplayer-board.html +41 -41
- package/templates/skills/pages/files/examples/report-page.html +125 -124
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +1 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/dist/cli-2pnswgh8.js +0 -189
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-vwmba1m2.js +0 -31
- package/dist/index-5r71rf98.js +0 -32
- package/dist/index-anmwxs87.js +0 -29
- package/dist/index-spp0ra5c.js +0 -36
- package/dist/lead-fjy418cw.js +0 -69
- package/dist/worker-0c5tyzqe.js +0 -69
- package/templates/schedules/daily-workflow-health-audit/config.json +0 -19
- package/templates/schedules/daily-workflow-health-audit/content.md +0 -191
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design version 2 head (`src/pages/page-head.ts`) and its serving through
|
|
3
|
+
* `/p/:id`:
|
|
4
|
+
* - `buildPageHeadV2` emits its parts in the required order: the layer
|
|
5
|
+
* statement is the first style, the Tailwind source comes before the
|
|
6
|
+
* pinned script, and the dark class script only in dark mode
|
|
7
|
+
* - the theme CSS carries every contract token, the aliases and print
|
|
8
|
+
* - the design CSS has every primitive and no color literal
|
|
9
|
+
* - serving resolves the theme: page `theme`, else `__swarm-theme`, else
|
|
10
|
+
* Hive, plus `themeTokens`; `__swarm-mode=dark` and print stay light
|
|
11
|
+
* - serving keeps a v2 page in standards mode: a doctype first, then the
|
|
12
|
+
* head after `<head>`, `<html>` or the doctype
|
|
13
|
+
* - a page with `COLOR_LITERAL_THRESHOLD` or more hardcoded colors stays
|
|
14
|
+
* light in dark mode
|
|
15
|
+
*/
|
|
16
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
17
|
+
import crypto from "node:crypto";
|
|
18
|
+
import { unlink } from "node:fs/promises";
|
|
19
|
+
import {
|
|
20
|
+
createServer as createHttpServer,
|
|
21
|
+
type IncomingMessage,
|
|
22
|
+
type Server,
|
|
23
|
+
type ServerResponse,
|
|
24
|
+
} from "node:http";
|
|
25
|
+
import { closeDb, initDb } from "../be/db";
|
|
26
|
+
import { handlePages } from "../http/pages";
|
|
27
|
+
import { handlePagesPublic } from "../http/pages-public";
|
|
28
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
29
|
+
import { COLOR_LITERAL_THRESHOLD } from "../pages/color-literals";
|
|
30
|
+
import {
|
|
31
|
+
buildPageHeadV2,
|
|
32
|
+
PAGE_HEAD_V1,
|
|
33
|
+
PAGE_MODE_PARAM,
|
|
34
|
+
PAGE_THEME_PARAM,
|
|
35
|
+
} from "../pages/page-head";
|
|
36
|
+
import { resolvePageTheme } from "../themes/page-theme";
|
|
37
|
+
import { getThemePreset } from "../themes/presets";
|
|
38
|
+
import { HIVE_PAGE_TOKENS, PAGE_THEME_TOKENS } from "../themes/tokens";
|
|
39
|
+
import { listenOnFreePort } from "./test-net";
|
|
40
|
+
|
|
41
|
+
const TEST_DB_PATH = "./test-pages-head-v2.sqlite";
|
|
42
|
+
|
|
43
|
+
const TAILWIND_SCRIPT =
|
|
44
|
+
'<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3/dist/index.global.js" integrity="sha384-2ql948lIdLcGEE0/qxNiudyTjgauA3RDJERu5xW75kFCvSl5a9odyQYCb6tEjnmB" crossorigin="anonymous"></script>';
|
|
45
|
+
const DARK_SCRIPT = '<script>document.documentElement.classList.add("dark")</script>';
|
|
46
|
+
const LAYER_STATEMENT = "<style>@layer properties, theme, base, components, utilities;</style>";
|
|
47
|
+
|
|
48
|
+
const PRIMITIVES = [
|
|
49
|
+
".swarm-page",
|
|
50
|
+
".swarm-eyebrow",
|
|
51
|
+
".swarm-lede",
|
|
52
|
+
".swarm-section",
|
|
53
|
+
".swarm-metrics",
|
|
54
|
+
".swarm-metric",
|
|
55
|
+
".swarm-metric-value",
|
|
56
|
+
".swarm-metric-label",
|
|
57
|
+
".swarm-panel",
|
|
58
|
+
".swarm-card",
|
|
59
|
+
".swarm-badge",
|
|
60
|
+
".swarm-badge[data-dot]",
|
|
61
|
+
".swarm-table-wrap",
|
|
62
|
+
".swarm-table",
|
|
63
|
+
".num",
|
|
64
|
+
".nowrap",
|
|
65
|
+
".swarm-button",
|
|
66
|
+
'[data-variant="primary"]',
|
|
67
|
+
'[data-variant="ghost"]',
|
|
68
|
+
".swarm-input",
|
|
69
|
+
".swarm-toolbar",
|
|
70
|
+
".swarm-segmented",
|
|
71
|
+
'[aria-pressed="true"]',
|
|
72
|
+
"[data-active]",
|
|
73
|
+
".swarm-callout",
|
|
74
|
+
".swarm-kv",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const BADGE_TONES = [
|
|
78
|
+
"neutral",
|
|
79
|
+
"primary",
|
|
80
|
+
"success",
|
|
81
|
+
"warning",
|
|
82
|
+
"error",
|
|
83
|
+
"info",
|
|
84
|
+
"pending",
|
|
85
|
+
"paused",
|
|
86
|
+
"active",
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
/** The text of a `<style id="...">` element. */
|
|
90
|
+
function styleText(html: string, id: string): string {
|
|
91
|
+
const match = new RegExp(`<style id="${id}">\\n([\\s\\S]*?)\\n</style>`).exec(html);
|
|
92
|
+
if (!match) throw new Error(`no <style id="${id}">`);
|
|
93
|
+
return match[1]!;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The body of the first rule whose text is `<selector> {`, up to its closing brace. */
|
|
97
|
+
function ruleBody(css: string, selector: string): string {
|
|
98
|
+
const start = css.indexOf(`${selector} {`);
|
|
99
|
+
if (start < 0) throw new Error(`no ${selector} rule`);
|
|
100
|
+
const open = start + selector.length + 2;
|
|
101
|
+
return css.slice(open, css.indexOf("}", open));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Declarations of the top-level rule whose line is exactly `<selector> {`. */
|
|
105
|
+
function declarations(css: string, selector: string): Record<string, string> {
|
|
106
|
+
const lines = css.split("\n");
|
|
107
|
+
const start = lines.indexOf(`${selector} {`);
|
|
108
|
+
if (start < 0) throw new Error(`no ${selector} rule`);
|
|
109
|
+
const out: Record<string, string> = {};
|
|
110
|
+
for (const line of lines.slice(start + 1)) {
|
|
111
|
+
if (line === "}") break;
|
|
112
|
+
const match = /^\s*([\w-]+):\s*(.+);$/.exec(line);
|
|
113
|
+
if (match) out[match[1]!] = match[2]!;
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const hiveHead = buildPageHeadV2({ theme: resolvePageTheme({}), mode: "light" });
|
|
119
|
+
const designCss = styleText(hiveHead, "swarm-design");
|
|
120
|
+
const baseLayer = designCss.slice(0, designCss.indexOf("\n@layer components {"));
|
|
121
|
+
const componentsLayer = designCss.slice(
|
|
122
|
+
designCss.indexOf("\n@layer components {"),
|
|
123
|
+
designCss.indexOf("\n@media print {"),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
describe("buildPageHeadV2", () => {
|
|
127
|
+
test("emits the parts in order, with the layer statement as the first style", () => {
|
|
128
|
+
expect(hiveHead.startsWith('<base target="_blank">\n')).toBe(true);
|
|
129
|
+
const firstStyle = hiveHead.indexOf("<style");
|
|
130
|
+
expect(hiveHead.slice(firstStyle).startsWith(LAYER_STATEMENT)).toBe(true);
|
|
131
|
+
const order = [
|
|
132
|
+
'<base target="_blank">',
|
|
133
|
+
LAYER_STATEMENT,
|
|
134
|
+
"https://fonts.googleapis.com/css2?family=Space+Grotesk",
|
|
135
|
+
'<style id="swarm-theme">',
|
|
136
|
+
'<style id="swarm-design">',
|
|
137
|
+
'style.type = "text/tailwindcss";',
|
|
138
|
+
TAILWIND_SCRIPT,
|
|
139
|
+
].map((part) => hiveHead.indexOf(part));
|
|
140
|
+
expect(order.every((index) => index >= 0)).toBe(true);
|
|
141
|
+
expect([...order].sort((a, b) => a - b)).toEqual(order);
|
|
142
|
+
expect(hiveHead).not.toContain("cdn.tailwindcss.com");
|
|
143
|
+
expect(hiveHead).not.toContain(DARK_SCRIPT);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("no @import sits in the markup, where the preload scanner would fetch it", () => {
|
|
147
|
+
// The scanner reads `@import` in every `<style>`, whatever its type, and
|
|
148
|
+
// requested /p/tailwindcss/theme and /p/tailwindcss/utilities (401).
|
|
149
|
+
const styles = [...hiveHead.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/g)].map((m) => m[1]);
|
|
150
|
+
expect(styles.length).toBeGreaterThan(0);
|
|
151
|
+
for (const css of styles) expect(css).not.toContain("@import");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("adds the dark class script only in dark mode, after the Tailwind script", () => {
|
|
155
|
+
const dark = buildPageHeadV2({ theme: resolvePageTheme({}), mode: "dark" });
|
|
156
|
+
expect(dark.endsWith(`${TAILWIND_SCRIPT}\n${DARK_SCRIPT}`)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("the Tailwind source imports theme and utilities only and maps every token", () => {
|
|
160
|
+
const json = /style\.textContent = (".*");\n/.exec(hiveHead)?.[1];
|
|
161
|
+
expect(json).toBeDefined();
|
|
162
|
+
// `<` is escaped, so the source cannot close its script.
|
|
163
|
+
expect(json).not.toContain("<");
|
|
164
|
+
const source = JSON.parse(json!) as string;
|
|
165
|
+
const lines = source.split("\n");
|
|
166
|
+
expect(lines.slice(0, 4)).toEqual([
|
|
167
|
+
'@import "tailwindcss/theme" layer(theme);',
|
|
168
|
+
'@import "tailwindcss/utilities" layer(utilities);',
|
|
169
|
+
"@custom-variant dark (&:where(.dark, .dark *));",
|
|
170
|
+
"@theme inline reference {",
|
|
171
|
+
]);
|
|
172
|
+
for (const token of PAGE_THEME_TOKENS) {
|
|
173
|
+
expect(lines).toContain(` ${token}: var(${token});`);
|
|
174
|
+
}
|
|
175
|
+
// A bare `@import "tailwindcss"` would bring Preflight back.
|
|
176
|
+
expect(source).not.toContain('@import "tailwindcss";');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("the theme CSS has every contract token in both modes, the aliases and print", () => {
|
|
180
|
+
const css = styleText(hiveHead, "swarm-theme");
|
|
181
|
+
const light = declarations(css, ":root");
|
|
182
|
+
const dark = declarations(css, ":root.dark");
|
|
183
|
+
expect(light["color-scheme"]).toBe("light");
|
|
184
|
+
expect(dark["color-scheme"]).toBe("dark");
|
|
185
|
+
for (const token of PAGE_THEME_TOKENS) {
|
|
186
|
+
expect(light[token]).toBe(HIVE_PAGE_TOKENS.light[token]);
|
|
187
|
+
expect(dark[token]).toBe(HIVE_PAGE_TOKENS.dark[token]);
|
|
188
|
+
}
|
|
189
|
+
expect(light["--swarm-bg"]).toBe("var(--color-background)");
|
|
190
|
+
expect(light["--swarm-primary"]).toBe("var(--color-primary)");
|
|
191
|
+
expect(css).toContain("@media print {\n :root.dark {\n color-scheme: light;");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("the design CSS layers every primitive and holds no color literal", () => {
|
|
195
|
+
const css = styleText(hiveHead, "swarm-design");
|
|
196
|
+
expect(css.startsWith("@layer base {")).toBe(true);
|
|
197
|
+
expect(css).toContain("\n@layer components {");
|
|
198
|
+
for (const primitive of PRIMITIVES) expect(css).toContain(primitive);
|
|
199
|
+
for (const tone of BADGE_TONES) expect(css).toContain(`[data-tone="${tone}"]`);
|
|
200
|
+
expect(css).not.toMatch(/#[0-9a-f]{3,8}\b|\brgba?\(|\bhsla?\(|\boklch\(/i);
|
|
201
|
+
const print = css.slice(css.indexOf("@media print {"));
|
|
202
|
+
expect(print).toContain(".no-print { display: none !important; }");
|
|
203
|
+
expect(print).toContain(
|
|
204
|
+
".swarm-panel, .swarm-card, swarm-diff, .swarm-metric { break-inside: avoid; }",
|
|
205
|
+
);
|
|
206
|
+
expect(print).toContain(
|
|
207
|
+
"html, body { background: white !important; color: black !important; }",
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("the control font reset has zero specificity and comes before the control looks", () => {
|
|
212
|
+
// A plain `button` selector (0,0,1) would beat the later `:where()` looks.
|
|
213
|
+
const reset = baseLayer.indexOf(
|
|
214
|
+
":where(button, input, optgroup, select, textarea), ::file-selector-button {",
|
|
215
|
+
);
|
|
216
|
+
expect(reset).toBeGreaterThan(-1);
|
|
217
|
+
expect(baseLayer).not.toContain(" button, input, optgroup");
|
|
218
|
+
expect(reset).toBeLessThan(baseLayer.indexOf(":where(input:not("));
|
|
219
|
+
expect(reset).toBeLessThan(baseLayer.indexOf(':where(button, input[type="button"]'));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("the base layer pads the body 16px at 640px and below", () => {
|
|
223
|
+
expect(baseLayer).toContain("@media (max-width: 640px) {\n body { padding: 16px; }\n }");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("the components layer resets diff tables, colors badge dots and grows toolbar inputs", () => {
|
|
227
|
+
expect(componentsLayer).toContain(
|
|
228
|
+
".swarm-diff-root :is(table, th, td) { margin: 0; border: 0; }",
|
|
229
|
+
);
|
|
230
|
+
expect(componentsLayer).toMatch(
|
|
231
|
+
/\.swarm-badge\[data-dot\]::before \{[^}]*background: var\(--swarm-dot, currentColor\);/,
|
|
232
|
+
);
|
|
233
|
+
expect(componentsLayer).toMatch(/\.swarm-badge \{[^}]*gap: 6px;/);
|
|
234
|
+
expect(componentsLayer).toContain("input:not([type])) { flex: 1 1 260px; }");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("the in-page link script follows <base> and keeps # links in the page", () => {
|
|
238
|
+
const script = /^<base target="_blank">\n<script>([\s\S]*?)<\/script>\n<style>/.exec(
|
|
239
|
+
hiveHead,
|
|
240
|
+
)?.[1];
|
|
241
|
+
expect(script).toBeDefined();
|
|
242
|
+
let listener: ((event: { target: unknown }) => void) | undefined;
|
|
243
|
+
let capture: unknown;
|
|
244
|
+
const document = {
|
|
245
|
+
addEventListener(type: string, handler: typeof listener, useCapture: unknown) {
|
|
246
|
+
if (type === "click") [listener, capture] = [handler, useCapture];
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
new Function("document", script!)(document);
|
|
250
|
+
expect(capture).toBe(true);
|
|
251
|
+
const attrs: Record<string, string> = {};
|
|
252
|
+
const link = { setAttribute: (name: string, value: string) => (attrs[name] = value) };
|
|
253
|
+
let selector = "";
|
|
254
|
+
listener!({
|
|
255
|
+
target: {
|
|
256
|
+
closest: (query: string) => {
|
|
257
|
+
selector = query;
|
|
258
|
+
return link;
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
expect(selector).toBe('a[href^="#"]:not([target])');
|
|
263
|
+
expect(attrs).toEqual({ target: "_self" });
|
|
264
|
+
// A click outside such a link, or with no element target, changes nothing.
|
|
265
|
+
listener!({ target: { closest: () => null } });
|
|
266
|
+
listener!({ target: null });
|
|
267
|
+
expect(PAGE_HEAD_V1).not.toContain('addEventListener("click"');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("the html element paints no background, so a body background reaches the canvas", () => {
|
|
271
|
+
expect(ruleBody(baseLayer, " html")).not.toContain("background");
|
|
272
|
+
expect(ruleBody(baseLayer, " body")).toContain("background-color: var(--color-background);");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("inline code clones its padding and radius onto each line of a wrapped chip", () => {
|
|
276
|
+
const code = ruleBody(baseLayer, " code");
|
|
277
|
+
expect(code).toContain("-webkit-box-decoration-break: clone;");
|
|
278
|
+
expect(code).toContain("box-decoration-break: clone;");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("the table wrap shows token edge shadows where columns are hidden", () => {
|
|
282
|
+
const wrap = ruleBody(componentsLayer, " .swarm-table-wrap");
|
|
283
|
+
const shadow = "color-mix(in oklab, var(--color-foreground) 14%, transparent)";
|
|
284
|
+
expect(wrap).toContain(
|
|
285
|
+
"linear-gradient(to right, var(--color-card) 30%, transparent) left / 40px 100% no-repeat local,",
|
|
286
|
+
);
|
|
287
|
+
expect(wrap).toContain(
|
|
288
|
+
"linear-gradient(to left, var(--color-card) 30%, transparent) right / 40px 100% no-repeat local,",
|
|
289
|
+
);
|
|
290
|
+
expect(wrap).toContain(
|
|
291
|
+
`linear-gradient(to right, ${shadow}, transparent) left / 14px 100% no-repeat scroll,`,
|
|
292
|
+
);
|
|
293
|
+
expect(wrap).toContain(
|
|
294
|
+
`linear-gradient(to left, ${shadow}, transparent) right / 14px 100% no-repeat scroll,`,
|
|
295
|
+
);
|
|
296
|
+
expect(wrap).toMatch(/var\(--color-card\);\n\s+border: 1px solid var\(--color-border\);/);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("a table footer gets a strong rule and weight 600, and the last body row keeps its rule", () => {
|
|
300
|
+
const footer = ruleBody(baseLayer, " tfoot :is(td, th)");
|
|
301
|
+
expect(footer).toContain("font-weight: 600;");
|
|
302
|
+
expect(footer).toContain("border-top: 1px solid var(--color-border);");
|
|
303
|
+
// Collapsed borders: the upper cell wins a same-width conflict, so the
|
|
304
|
+
// shared line takes the strong color from the body side too.
|
|
305
|
+
expect(baseLayer).toContain(
|
|
306
|
+
"tbody:has(+ tfoot) > tr:last-child > :is(td, th) { border-bottom-color: var(--color-border); }",
|
|
307
|
+
);
|
|
308
|
+
// Inside a wrap, only the table's last row drops its rule (the wrap border
|
|
309
|
+
// is the edge), not the last body row before a footer.
|
|
310
|
+
expect(componentsLayer).toContain(
|
|
311
|
+
".swarm-table-wrap > .swarm-table > :last-child > tr:last-child > :is(td, th) { border-bottom: 0; }",
|
|
312
|
+
);
|
|
313
|
+
expect(componentsLayer).not.toContain(".swarm-table tbody tr:last-child");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("print shows a footer once and lets wide swarm tables wrap at 12px", () => {
|
|
317
|
+
const print = designCss.slice(designCss.indexOf("\n@media print {"));
|
|
318
|
+
expect(print).toContain("tfoot { display: table-row-group; }");
|
|
319
|
+
expect(print).toContain(".swarm-table { font-size: 12px; }");
|
|
320
|
+
expect(print).toContain(".swarm-table :is(th, td):not(.num) { white-space: normal; }");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("the version 1 head stays the original Tailwind v3 head", () => {
|
|
324
|
+
expect(PAGE_HEAD_V1).toContain('<script src="https://cdn.tailwindcss.com"></script>');
|
|
325
|
+
expect(PAGE_HEAD_V1).toContain("--swarm-bg: #fbfbfa;");
|
|
326
|
+
expect(PAGE_HEAD_V1).not.toContain("@tailwindcss/browser");
|
|
327
|
+
expect(PAGE_THEME_PARAM).toBe("__swarm-theme");
|
|
328
|
+
expect(PAGE_MODE_PARAM).toBe("__swarm-mode");
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe("GET /p/:id serves the head of the page's design version", () => {
|
|
333
|
+
let server: Server;
|
|
334
|
+
let base = "";
|
|
335
|
+
const headers = { "Content-Type": "application/json", "X-Agent-ID": crypto.randomUUID() };
|
|
336
|
+
const html =
|
|
337
|
+
'<!doctype html><html><head><title>T</title></head><body><main class="swarm-page"><h1>Hi</h1></main></body></html>';
|
|
338
|
+
|
|
339
|
+
async function createPage(slug: string, extra: Record<string, unknown> = {}): Promise<string> {
|
|
340
|
+
const res = await fetch(`${base}/api/pages`, {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers,
|
|
343
|
+
body: JSON.stringify({
|
|
344
|
+
slug,
|
|
345
|
+
title: slug,
|
|
346
|
+
contentType: "text/html",
|
|
347
|
+
authMode: "public",
|
|
348
|
+
body: html,
|
|
349
|
+
...extra,
|
|
350
|
+
}),
|
|
351
|
+
});
|
|
352
|
+
expect(res.status).toBe(201);
|
|
353
|
+
return ((await res.json()) as { id: string }).id;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function serve(id: string, query = ""): Promise<string> {
|
|
357
|
+
const res = await fetch(`${base}/p/${id}${query}`);
|
|
358
|
+
expect(res.status).toBe(200);
|
|
359
|
+
return res.text();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function lightTokens(page: string): Record<string, string> {
|
|
363
|
+
return declarations(styleText(page, "swarm-theme"), ":root");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
beforeAll(async () => {
|
|
367
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
368
|
+
try {
|
|
369
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
370
|
+
} catch {}
|
|
371
|
+
}
|
|
372
|
+
initDb(TEST_DB_PATH);
|
|
373
|
+
server = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
374
|
+
const pathSegments = getPathSegments(req.url || "");
|
|
375
|
+
const queryParams = parseQueryParams(req.url || "");
|
|
376
|
+
const myAgentId = req.headers["x-agent-id"] as string | undefined;
|
|
377
|
+
if (await handlePagesPublic(req, res, pathSegments, queryParams)) return;
|
|
378
|
+
if (await handlePages(req, res, pathSegments, queryParams, myAgentId)) return;
|
|
379
|
+
res.writeHead(404);
|
|
380
|
+
res.end("not found");
|
|
381
|
+
});
|
|
382
|
+
base = `http://localhost:${await listenOnFreePort(server)}`;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
afterAll(async () => {
|
|
386
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
387
|
+
closeDb();
|
|
388
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
389
|
+
try {
|
|
390
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
391
|
+
} catch {}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("a new page gets the v2 head: layer statement first, pinned v4, tokens, primitives", async () => {
|
|
396
|
+
const page = await serve(await createPage("v2-default"));
|
|
397
|
+
const head = page.slice(page.indexOf("<head>"), page.indexOf("</head>"));
|
|
398
|
+
expect(head.indexOf("<style")).toBe(head.indexOf(LAYER_STATEMENT));
|
|
399
|
+
expect(head).toContain(TAILWIND_SCRIPT);
|
|
400
|
+
expect(head).not.toContain("cdn.tailwindcss.com");
|
|
401
|
+
expect(head).not.toContain(DARK_SCRIPT);
|
|
402
|
+
expect(head).toContain("--swarm-text: var(--color-foreground);");
|
|
403
|
+
expect(head).toContain(".swarm-metric-value");
|
|
404
|
+
expect(lightTokens(page)["--color-primary"]).toBe(HIVE_PAGE_TOKENS.light["--color-primary"]);
|
|
405
|
+
// The SDK and the built-in elements still follow the head.
|
|
406
|
+
expect(page.indexOf("class SwarmSDK")).toBeGreaterThan(page.indexOf(TAILWIND_SCRIPT));
|
|
407
|
+
expect(page).toContain("customElements.define('swarm-diff'");
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("a v2 body without a doctype or a <head> is served in standards mode", async () => {
|
|
411
|
+
const cases: [body: string, start: string, end: string][] = [
|
|
412
|
+
// A fragment: the doctype is prepended and the head follows it.
|
|
413
|
+
["<h1>Hi</h1>", '<!doctype html><base target="_blank">', "</script><h1>Hi</h1>"],
|
|
414
|
+
// A doctype and no <html>: the head follows the doctype, not doubled.
|
|
415
|
+
[
|
|
416
|
+
"\n<!DOCTYPE html>\n<p>x</p>",
|
|
417
|
+
'\n<!DOCTYPE html><base target="_blank">',
|
|
418
|
+
"</script>\n<p>x</p>",
|
|
419
|
+
],
|
|
420
|
+
// An <html> tag and no <head>: the head follows <html ...>.
|
|
421
|
+
[
|
|
422
|
+
'<!doctype html>\n<html lang="en"><body>x</body></html>',
|
|
423
|
+
'<!doctype html>\n<html lang="en"><base target="_blank">',
|
|
424
|
+
"</script><body>x</body></html>",
|
|
425
|
+
],
|
|
426
|
+
// A <head> and no doctype: the doctype is prepended, the head stays put.
|
|
427
|
+
[
|
|
428
|
+
"<html><head><title>T</title></head><body>x</body></html>",
|
|
429
|
+
'<!doctype html><html><head><base target="_blank">',
|
|
430
|
+
"</script><title>T</title></head><body>x</body></html>",
|
|
431
|
+
],
|
|
432
|
+
];
|
|
433
|
+
for (const [index, [body, start, end]] of cases.entries()) {
|
|
434
|
+
const page = await serve(await createPage(`standards-${index}`, { body }));
|
|
435
|
+
expect(page.startsWith(start)).toBe(true);
|
|
436
|
+
expect(page.endsWith(end)).toBe(true);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("a body full of unclosed <head tokens is served in linear time", async () => {
|
|
441
|
+
// `/<head\b[^>]*>/i` rescanned the rest of the body for each token: 240k
|
|
442
|
+
// chars took 1.6 s and a 5 MB body would block the API for minutes.
|
|
443
|
+
const body = "<head ".repeat(100_000);
|
|
444
|
+
for (const designVersion of [1, 2]) {
|
|
445
|
+
const id = await createPage(`head-tokens-v${designVersion}`, { body, designVersion });
|
|
446
|
+
const started = performance.now();
|
|
447
|
+
const page = await serve(id);
|
|
448
|
+
expect(performance.now() - started).toBeLessThan(500);
|
|
449
|
+
expect(page.length).toBeGreaterThan(body.length);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test("a version 1 page keeps the exact old head and ignores the params", async () => {
|
|
454
|
+
// Frozen from src/http/pages-public.ts at main commit 3d6d8a750.
|
|
455
|
+
const baseline = await Bun.file(
|
|
456
|
+
new URL("./fixtures/pages/head-v1.html", import.meta.url),
|
|
457
|
+
).text();
|
|
458
|
+
const id = await createPage("v1-page", { designVersion: 1 });
|
|
459
|
+
const page = await serve(id, `?${PAGE_THEME_PARAM}=github&${PAGE_MODE_PARAM}=dark`);
|
|
460
|
+
expect(page).toContain(`<head>${baseline}<script>`);
|
|
461
|
+
expect(page).toContain("--swarm-bg: #fbfbfa");
|
|
462
|
+
expect(page).not.toContain("@tailwindcss/browser");
|
|
463
|
+
expect(page).not.toContain(DARK_SCRIPT);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("__swarm-theme picks the preset when the page has no theme; unknown ids fall back", async () => {
|
|
467
|
+
const id = await createPage("viewer-theme");
|
|
468
|
+
const github = getThemePreset("github")!;
|
|
469
|
+
expect(lightTokens(await serve(id, `?${PAGE_THEME_PARAM}=github`))["--color-primary"]).toBe(
|
|
470
|
+
github.vars.light["--color-primary"],
|
|
471
|
+
);
|
|
472
|
+
expect(github.vars.light["--color-primary"]).not.toBe(
|
|
473
|
+
HIVE_PAGE_TOKENS.light["--color-primary"],
|
|
474
|
+
);
|
|
475
|
+
expect(lightTokens(await serve(id, `?${PAGE_THEME_PARAM}=nope`))).toEqual(
|
|
476
|
+
lightTokens(await serve(id)),
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("the page theme wins over __swarm-theme", async () => {
|
|
481
|
+
const id = await createPage("page-theme", { theme: "paper" });
|
|
482
|
+
const tokens = lightTokens(await serve(id, `?${PAGE_THEME_PARAM}=github`));
|
|
483
|
+
const paper = getThemePreset("paper")!;
|
|
484
|
+
expect(tokens["--color-primary"]).toBe(paper.vars.light["--color-primary"]);
|
|
485
|
+
expect(tokens["--color-background"]).toBe(paper.vars.light["--color-background"]);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("themeTokens override the preset", async () => {
|
|
489
|
+
const id = await createPage("theme-tokens", {
|
|
490
|
+
themeTokens: {
|
|
491
|
+
light: { "--color-primary": "oklch(0.5 0.2 150)" },
|
|
492
|
+
dark: { "--color-primary": "oklch(0.7 0.15 150)" },
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
const css = styleText(await serve(id, `?${PAGE_THEME_PARAM}=github`), "swarm-theme");
|
|
496
|
+
expect(declarations(css, ":root")["--color-primary"]).toBe("oklch(0.5 0.2 150)");
|
|
497
|
+
expect(declarations(css, ":root.dark")["--color-primary"]).toBe("oklch(0.7 0.15 150)");
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("__swarm-mode=dark adds the dark class script; other values stay light", async () => {
|
|
501
|
+
const id = await createPage("dark-mode");
|
|
502
|
+
expect(await serve(id, `?${PAGE_MODE_PARAM}=dark`)).toContain(DARK_SCRIPT);
|
|
503
|
+
expect(await serve(id, `?${PAGE_MODE_PARAM}=light`)).not.toContain(DARK_SCRIPT);
|
|
504
|
+
expect(await serve(id, `?${PAGE_MODE_PARAM}=DARK`)).not.toContain(DARK_SCRIPT);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("a page with 8 or more hardcoded colors stays light in dark mode; 7 still go dark", async () => {
|
|
508
|
+
expect(COLOR_LITERAL_THRESHOLD).toBe(8);
|
|
509
|
+
const body = (count: number) =>
|
|
510
|
+
`<!doctype html><html><head><style>${Array.from(
|
|
511
|
+
{ length: count },
|
|
512
|
+
(_, i) => `.c${i}{color:rgb(${i} 0 0)}`,
|
|
513
|
+
).join("")}</style></head><body><h1>Hi</h1></body></html>`;
|
|
514
|
+
const hardcoded = await createPage("hardcoded-8", { body: body(8) });
|
|
515
|
+
const page = await serve(hardcoded, `?${PAGE_MODE_PARAM}=dark&${PAGE_THEME_PARAM}=github`);
|
|
516
|
+
expect(page).not.toContain(DARK_SCRIPT);
|
|
517
|
+
// The viewer's preset still applies, in light mode.
|
|
518
|
+
expect(lightTokens(page)["--color-primary"]).toBe(
|
|
519
|
+
getThemePreset("github")!.vars.light["--color-primary"],
|
|
520
|
+
);
|
|
521
|
+
const tokens = await createPage("hardcoded-7", { body: body(7) });
|
|
522
|
+
expect(await serve(tokens, `?${PAGE_MODE_PARAM}=dark`)).toContain(DARK_SCRIPT);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("print still works for both versions, and a v2 print is light", async () => {
|
|
526
|
+
const v2 = await serve(await createPage("print-v2"), `?print=1&${PAGE_MODE_PARAM}=dark`);
|
|
527
|
+
expect(v2).toContain("window.print()");
|
|
528
|
+
expect(v2).toContain('<style id="swarm-theme">');
|
|
529
|
+
expect(v2).not.toContain(DARK_SCRIPT);
|
|
530
|
+
const v1 = await serve(await createPage("print-v1", { designVersion: 1 }), "?print=1");
|
|
531
|
+
expect(v1).toContain("window.print()");
|
|
532
|
+
expect(v1).toContain(PAGE_HEAD_V1);
|
|
533
|
+
});
|
|
534
|
+
});
|