@supportpages.io/wtfm 0.0.0-stage → 0.3.0

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.
Files changed (275) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +23 -0
  3. package/README.md +392 -2
  4. package/dist/actions.d.ts +98 -0
  5. package/dist/actions.js +86 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/agent-settings.d.ts +22 -0
  8. package/dist/agent-settings.js +35 -0
  9. package/dist/agent-settings.js.map +1 -0
  10. package/dist/api.d.ts +14 -0
  11. package/dist/api.js +159 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/article-link.d.ts +16 -0
  14. package/dist/article-link.js +25 -0
  15. package/dist/article-link.js.map +1 -0
  16. package/dist/artifacts.d.ts +45 -0
  17. package/dist/artifacts.js +144 -0
  18. package/dist/artifacts.js.map +1 -0
  19. package/dist/brand.d.ts +4 -0
  20. package/dist/brand.js +9 -0
  21. package/dist/brand.js.map +1 -0
  22. package/dist/bridge.d.ts +2964 -0
  23. package/dist/bridge.js +1036 -0
  24. package/dist/bridge.js.map +1 -0
  25. package/dist/capacity.d.ts +5 -0
  26. package/dist/capacity.js +11 -0
  27. package/dist/capacity.js.map +1 -0
  28. package/dist/credentials.d.ts +13 -0
  29. package/dist/credentials.js +88 -0
  30. package/dist/credentials.js.map +1 -0
  31. package/dist/development-tls.d.ts +8 -0
  32. package/dist/development-tls.js +38 -0
  33. package/dist/development-tls.js.map +1 -0
  34. package/dist/errors.d.ts +15 -0
  35. package/dist/errors.js +18 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/export.d.ts +11 -0
  38. package/dist/export.js +91 -0
  39. package/dist/export.js.map +1 -0
  40. package/dist/hosted-operations.d.ts +108 -0
  41. package/dist/hosted-operations.js +120 -0
  42. package/dist/hosted-operations.js.map +1 -0
  43. package/dist/hosting-benefits.d.ts +66 -0
  44. package/dist/hosting-benefits.js +67 -0
  45. package/dist/hosting-benefits.js.map +1 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +49 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/local-inventory.d.ts +22 -0
  50. package/dist/local-inventory.js +59 -0
  51. package/dist/local-inventory.js.map +1 -0
  52. package/dist/local-setup.d.ts +208 -0
  53. package/dist/local-setup.js +140 -0
  54. package/dist/local-setup.js.map +1 -0
  55. package/dist/pairing.d.ts +71 -0
  56. package/dist/pairing.js +235 -0
  57. package/dist/pairing.js.map +1 -0
  58. package/dist/preferences.d.ts +18 -0
  59. package/dist/preferences.js +44 -0
  60. package/dist/preferences.js.map +1 -0
  61. package/dist/progress.d.ts +205 -0
  62. package/dist/progress.js +224 -0
  63. package/dist/progress.js.map +1 -0
  64. package/dist/reminders.d.ts +31 -0
  65. package/dist/reminders.js +73 -0
  66. package/dist/reminders.js.map +1 -0
  67. package/dist/replace-connection.d.ts +6 -0
  68. package/dist/replace-connection.js +77 -0
  69. package/dist/replace-connection.js.map +1 -0
  70. package/dist/repository-actions.d.ts +18 -0
  71. package/dist/repository-actions.js +9 -0
  72. package/dist/repository-actions.js.map +1 -0
  73. package/dist/repository-benefits.d.ts +96 -0
  74. package/dist/repository-benefits.js +64 -0
  75. package/dist/repository-benefits.js.map +1 -0
  76. package/dist/run-update.d.ts +17 -0
  77. package/dist/run-update.js +27 -0
  78. package/dist/run-update.js.map +1 -0
  79. package/dist/runs.d.ts +541 -0
  80. package/dist/runs.js +146 -0
  81. package/dist/runs.js.map +1 -0
  82. package/dist/runtime.d.ts +12 -0
  83. package/dist/runtime.js +58 -0
  84. package/dist/runtime.js.map +1 -0
  85. package/dist/schema.d.ts +277 -0
  86. package/dist/schema.js +66 -0
  87. package/dist/schema.js.map +1 -0
  88. package/dist/server.d.ts +4 -0
  89. package/dist/server.js +298 -0
  90. package/dist/server.js.map +1 -0
  91. package/dist/session.d.ts +1714 -0
  92. package/dist/session.js +619 -0
  93. package/dist/session.js.map +1 -0
  94. package/dist/settings.d.ts +9 -0
  95. package/dist/settings.js +21 -0
  96. package/dist/settings.js.map +1 -0
  97. package/dist/sync.d.ts +495 -0
  98. package/dist/sync.js +191 -0
  99. package/dist/sync.js.map +1 -0
  100. package/dist/telemetry-scrub.d.ts +13 -0
  101. package/dist/telemetry-scrub.js +51 -0
  102. package/dist/telemetry-scrub.js.map +1 -0
  103. package/dist/telemetry.d.ts +73 -0
  104. package/dist/telemetry.js +173 -0
  105. package/dist/telemetry.js.map +1 -0
  106. package/dist/walkthroughs.d.ts +224 -0
  107. package/dist/walkthroughs.js +109 -0
  108. package/dist/walkthroughs.js.map +1 -0
  109. package/dist/workspace.d.ts +14 -0
  110. package/dist/workspace.js +127 -0
  111. package/dist/workspace.js.map +1 -0
  112. package/dist/writer-agent.d.ts +21 -0
  113. package/dist/writer-agent.js +27 -0
  114. package/dist/writer-agent.js.map +1 -0
  115. package/dist/writer-entry.d.ts +171 -0
  116. package/dist/writer-entry.js +233 -0
  117. package/dist/writer-entry.js.map +1 -0
  118. package/dist/writing-style.d.ts +7 -0
  119. package/dist/writing-style.js +52 -0
  120. package/dist/writing-style.js.map +1 -0
  121. package/engine/SYNC.json +4 -0
  122. package/engine/VERSION +1 -0
  123. package/engine/detect-project/README.md +141 -0
  124. package/engine/detect-project/SKILL.md +1421 -0
  125. package/engine/detect-project/assets/desktop/desktop-frame.css +428 -0
  126. package/engine/detect-project/assets/game/game-frame.css +132 -0
  127. package/engine/detect-project/assets/macosui/LICENSE-puppertino.txt +21 -0
  128. package/engine/detect-project/assets/macosui/VERSIONS.txt +1 -0
  129. package/engine/detect-project/assets/macosui/fonts.css +15 -0
  130. package/engine/detect-project/assets/macosui/macos-frame.css +481 -0
  131. package/engine/detect-project/assets/macosui/puppertino.css +2153 -0
  132. package/engine/detect-project/assets/mobileui/LICENSE-fonts.txt +13 -0
  133. package/engine/detect-project/assets/mobileui/LICENSE-framework7.txt +52 -0
  134. package/engine/detect-project/assets/mobileui/VERSIONS.txt +6 -0
  135. package/engine/detect-project/assets/mobileui/device-frame.css +316 -0
  136. package/engine/detect-project/assets/mobileui/f7-color-theme.mjs +1345 -0
  137. package/engine/detect-project/assets/mobileui/f7-icons-names.json +1254 -0
  138. package/engine/detect-project/assets/mobileui/fonts.css +16 -0
  139. package/engine/detect-project/assets/mobileui/framework7-components.css +39 -0
  140. package/engine/detect-project/assets/mobileui/framework7-core.css +5245 -0
  141. package/engine/detect-project/assets/mobileui/icons.css +31 -0
  142. package/engine/detect-project/assets/mobileui/md3-defaults.css +89 -0
  143. package/engine/detect-project/assets/mobileui/platforms.json +46 -0
  144. package/engine/detect-project/assets/tailwind-fallback.css +1729 -0
  145. package/engine/detect-project/assets/webtui/LICENSE-webtui.txt +28 -0
  146. package/engine/detect-project/assets/webtui/VERSIONS.txt +7 -0
  147. package/engine/detect-project/assets/webtui/terminal-frame.css +195 -0
  148. package/engine/detect-project/assets/webtui/theme-catppuccin.css +1 -0
  149. package/engine/detect-project/assets/webtui/theme-everforest.css +1 -0
  150. package/engine/detect-project/assets/webtui/theme-gruvbox.css +1 -0
  151. package/engine/detect-project/assets/webtui/theme-nord.css +1 -0
  152. package/engine/detect-project/assets/webtui/theme-vitesse.css +1 -0
  153. package/engine/detect-project/assets/webtui/themes.json +37 -0
  154. package/engine/detect-project/assets/webtui/webtui-core.css +1 -0
  155. package/engine/detect-project/assets/win32ui/7css.css +2 -0
  156. package/engine/detect-project/assets/win32ui/LICENSE-7css.txt +21 -0
  157. package/engine/detect-project/assets/win32ui/VERSIONS.txt +1 -0
  158. package/engine/detect-project/assets/win32ui/win32-frame.css +278 -0
  159. package/engine/detect-project/package-lock.json +12 -0
  160. package/engine/detect-project/package.json +10 -0
  161. package/engine/detect-project/scripts/apply_runtime_profiles.js +313 -0
  162. package/engine/detect-project/scripts/check_css_health.js +412 -0
  163. package/engine/detect-project/scripts/check_project_map.js +150 -0
  164. package/engine/detect-project/scripts/check_runtime_coverage.js +311 -0
  165. package/engine/detect-project/scripts/check_runtime_recipe_quality.js +184 -0
  166. package/engine/detect-project/scripts/classify_app_type.sh +246 -0
  167. package/engine/detect-project/scripts/classify_surface.sh +95 -0
  168. package/engine/detect-project/scripts/classify_workspace.js +39 -0
  169. package/engine/detect-project/scripts/compile_css.sh +447 -0
  170. package/engine/detect-project/scripts/detect_static.js +645 -0
  171. package/engine/detect-project/scripts/detect_structure.js +187 -0
  172. package/engine/detect-project/scripts/include_census.js +451 -0
  173. package/engine/detect-project/scripts/json_get.js +142 -0
  174. package/engine/detect-project/scripts/merge_json.js +52 -0
  175. package/engine/detect-project/scripts/recommend_model_tier.js +252 -0
  176. package/engine/detect-project/scripts/resolve_route_chains.js +135 -0
  177. package/engine/detect-project/scripts/run_css_build.sh +40 -0
  178. package/engine/detect-project/scripts/sanitize_css.js +83 -0
  179. package/engine/detect-project/scripts/test_classify_surface.js +108 -0
  180. package/engine/detect-project/scripts/test_node_helpers.js +142 -0
  181. package/engine/detect-project/scripts/test_recommend_model_tier.js +119 -0
  182. package/engine/detect-project/scripts/test_resolve_route_chains.js +182 -0
  183. package/engine/detect-project/scripts/test_runtime_coverage.js +323 -0
  184. package/engine/detect-project/scripts/test_runtime_recipe_quality.js +187 -0
  185. package/engine/detect-project/scripts/theme_overrides.js +169 -0
  186. package/engine/detect-project/scripts/write_branding.js +129 -0
  187. package/engine/generate-illustrated-article/SKILL.md +461 -0
  188. package/engine/generate-illustrated-article/contracts/desktop.md +90 -0
  189. package/engine/generate-illustrated-article/contracts/game.md +38 -0
  190. package/engine/generate-illustrated-article/contracts/label-evidence.md +38 -0
  191. package/engine/generate-illustrated-article/contracts/macos.md +79 -0
  192. package/engine/generate-illustrated-article/contracts/mobile.md +40 -0
  193. package/engine/generate-illustrated-article/contracts/terminal.md +29 -0
  194. package/engine/generate-illustrated-article/contracts/win32.md +43 -0
  195. package/engine/generate-illustrated-article/package-lock.json +366 -0
  196. package/engine/generate-illustrated-article/package.json +15 -0
  197. package/engine/generate-illustrated-article/scripts/article_blocks.js +34 -0
  198. package/engine/generate-illustrated-article/scripts/check_article_json.js +105 -0
  199. package/engine/generate-illustrated-article/scripts/emit_walkthrough_signals.js +100 -0
  200. package/engine/generate-illustrated-article/scripts/extract_images.js +187 -0
  201. package/engine/generate-illustrated-article/scripts/generate_content_images.js +376 -0
  202. package/engine/generate-illustrated-article/scripts/include_census.js +451 -0
  203. package/engine/generate-illustrated-article/scripts/inject_assets.js +1691 -0
  204. package/engine/generate-illustrated-article/scripts/jit_mockup_css.js +191 -0
  205. package/engine/generate-illustrated-article/scripts/label_evidence.js +87 -0
  206. package/engine/generate-illustrated-article/scripts/lint_article_copy.js +252 -0
  207. package/engine/generate-illustrated-article/scripts/lint_mockup_fidelity.js +2403 -0
  208. package/engine/generate-illustrated-article/scripts/polish_tickets.js +1148 -0
  209. package/engine/generate-illustrated-article/scripts/related_repos.sh +52 -0
  210. package/engine/generate-illustrated-article/scripts/render_all.js +177 -0
  211. package/engine/generate-illustrated-article/scripts/render_mockup.js +665 -0
  212. package/engine/generate-illustrated-article/scripts/render_ready.js +164 -0
  213. package/engine/generate-illustrated-article/scripts/resolve_workspace.sh +86 -0
  214. package/engine/generate-illustrated-article/scripts/runtime_region_geometry.js +55 -0
  215. package/engine/generate-illustrated-article/scripts/source_paths.js +49 -0
  216. package/engine/generate-illustrated-article/scripts/test_control_visibility.js +30 -0
  217. package/engine/generate-illustrated-article/scripts/test_emit_walkthrough_signals.js +157 -0
  218. package/engine/generate-illustrated-article/scripts/test_generate_content_images.js +168 -0
  219. package/engine/generate-illustrated-article/scripts/test_include_census.js +126 -0
  220. package/engine/generate-illustrated-article/scripts/test_label_evidence.js +49 -0
  221. package/engine/generate-illustrated-article/scripts/test_lint_article_copy.js +138 -0
  222. package/engine/generate-illustrated-article/scripts/test_lint_mockup_fidelity.js +653 -0
  223. package/engine/generate-illustrated-article/scripts/test_node_ports.js +152 -0
  224. package/engine/generate-illustrated-article/scripts/test_polish_tickets.js +481 -0
  225. package/engine/generate-illustrated-article/scripts/test_related_repos.js +64 -0
  226. package/engine/generate-illustrated-article/scripts/test_render_ready.js +85 -0
  227. package/engine/generate-illustrated-article/scripts/test_source_paths.js +68 -0
  228. package/engine/generate-illustrated-article/scripts/trace_hook.js +79 -0
  229. package/engine/generate-illustrated-article/scripts/trace_hook.sh +4 -0
  230. package/engine/generate-illustrated-article/scripts/validate_html.js +114 -0
  231. package/engine/generate-illustrated-article/scripts/watermark.js +69 -0
  232. package/install.sh +14 -0
  233. package/package.json +57 -4
  234. package/scripts/auth.mjs +33 -0
  235. package/scripts/auto-update.mjs +7 -0
  236. package/scripts/build-plugin.mjs +58 -0
  237. package/scripts/build-release.mjs +94 -0
  238. package/scripts/check-release.mjs +40 -0
  239. package/scripts/check-runtime.mjs +10 -0
  240. package/scripts/cli.mjs +8 -0
  241. package/scripts/install.mjs +64 -0
  242. package/scripts/lib/agent-runner.mjs +181 -0
  243. package/scripts/lib/agent-settings.mjs +215 -0
  244. package/scripts/lib/article-skills.mjs +82 -0
  245. package/scripts/lib/auto-update.mjs +50 -0
  246. package/scripts/lib/brand.mjs +9 -0
  247. package/scripts/lib/browser.mjs +12 -0
  248. package/scripts/lib/claude-connection.mjs +33 -0
  249. package/scripts/lib/claude-permissions.mjs +47 -0
  250. package/scripts/lib/claude-plugin.mjs +15 -0
  251. package/scripts/lib/claude-writer.mjs +10 -0
  252. package/scripts/lib/cli-main.mjs +85 -0
  253. package/scripts/lib/cli.mjs +888 -0
  254. package/scripts/lib/codex-config.mjs +51 -0
  255. package/scripts/lib/codex-integration.mjs +61 -0
  256. package/scripts/lib/codex-skill.mjs +34 -0
  257. package/scripts/lib/harness-models.mjs +109 -0
  258. package/scripts/lib/install.mjs +269 -0
  259. package/scripts/lib/managed-writer.mjs +38 -0
  260. package/scripts/lib/planning.mjs +263 -0
  261. package/scripts/lib/prepare-update.mjs +85 -0
  262. package/scripts/lib/refresh-writers.mjs +12 -0
  263. package/scripts/lib/remove.mjs +167 -0
  264. package/scripts/lib/renderer.mjs +34 -0
  265. package/scripts/lib/terminal.mjs +252 -0
  266. package/scripts/lib/uninit.mjs +72 -0
  267. package/scripts/lib/update.mjs +55 -0
  268. package/scripts/lib/writer-recovery.mjs +35 -0
  269. package/scripts/lib/yolo.mjs +362 -0
  270. package/scripts/plugin-session.mjs +65 -0
  271. package/scripts/prepare-update.mjs +12 -0
  272. package/scripts/publish-release.mjs +92 -0
  273. package/scripts/skills/supportpages/SKILL.md +120 -0
  274. package/scripts/smoke-release.mjs +101 -0
  275. package/server.json +27 -0
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Tests for the Node scripts that replaced the Python ports and jq filters:
5
+ // check_article_json, trace_hook, extract_images and inject_assets.
6
+
7
+ const assert = require('assert');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+ const { spawnSync } = require('child_process');
12
+
13
+ const { check } = require('./check_article_json.js');
14
+ const { record } = require('./trace_hook.js');
15
+ const { walk, signature } = require('./extract_images.js');
16
+ const {
17
+ buildCssHeadBlock, desktopWindowBgBridge, fixedOffsetBridgeCss, fontDescriptorToUrl,
18
+ firstGoogleFontFamily, injectCss, findLogo,
19
+ } = require('./inject_assets.js');
20
+
21
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'article-ports-'));
22
+ const run = (script, args, options = {}) => spawnSync(process.execPath, [path.join(__dirname, script), ...args], { encoding: 'utf8', ...options });
23
+
24
+ // check_article_json
25
+ {
26
+ const article = {
27
+ schema_version: 2, title: 'Invite a teammate', article_type: 'how-to',
28
+ blocks: [
29
+ { id: 'lead', type: 'prose', presentation: 'lead', content: 'Add people to your workspace.' },
30
+ { id: 'open-settings', type: 'section', presentation: 'numbered', title: 'Open settings', content: 'Click Settings.', has_image: false },
31
+ { id: 'tips', type: 'list', presentation: 'tips', title: 'Tips', items: ['Use work email'] },
32
+ ],
33
+ };
34
+ assert.deepStrictEqual(check(article), []);
35
+ const broken = structuredClone(article);
36
+ broken.blocks[1].id = 'lead';
37
+ broken.blocks[0].content = 'Run ```npm i```';
38
+ broken.blocks[2].presentation = 'grid';
39
+ const problems = check(broken).join('\n');
40
+ assert.match(problems, /duplicate block id/);
41
+ assert.match(problems, /code fence/);
42
+ assert.match(problems, /presentation must be one of bullets, checklist, tips/);
43
+ assert.match(check({ ...article, blocks: [] }).join('\n'), /non-empty array/);
44
+ }
45
+
46
+ // trace_hook: record shape and the run-scoped append.
47
+ {
48
+ const entry = record({ session_id: 's', tool_name: 'Bash', tool_input: { command: 'x'.repeat(600), path: 'src' } }, new Date('2026-09-22T10:00:00.123Z'));
49
+ assert.deepStrictEqual(Object.keys(entry), ['ts', 'session', 'tool', 'file', 'pattern', 'path', 'glob', 'command']);
50
+ assert.strictEqual(entry.ts, '2026-09-22T10:00:00Z');
51
+ assert.strictEqual(entry.command.length, 500);
52
+ assert.strictEqual(entry.path, 'src');
53
+ assert.strictEqual(entry.file, null);
54
+
55
+ const cwd = fs.mkdtempSync(path.join(tmp, 'trace-'));
56
+ const payload = JSON.stringify({ cwd, tool_name: 'Read', tool_input: { file_path: '/a.md' } });
57
+ assert.strictEqual(run('trace_hook.js', [], { input: payload }).status, 0);
58
+ assert(!fs.existsSync(path.join(cwd, '.rtfm-trace')), 'no trace outside a run');
59
+ fs.mkdirSync(path.join(cwd, '.rtfm-trace'));
60
+ fs.writeFileSync(path.join(cwd, '.rtfm-trace', 'CURRENT'), 'my-run\textra\n');
61
+ assert.strictEqual(run('trace_hook.js', [], { input: payload }).status, 0);
62
+ assert.strictEqual(run('trace_hook.js', [], { input: 'not json' }).status, 0);
63
+ const lines = fs.readFileSync(path.join(cwd, '.rtfm-trace', 'my-run.ndjson'), 'utf8').trim().split('\n');
64
+ assert.strictEqual(lines.length, 1);
65
+ assert.strictEqual(JSON.parse(lines[0]).file, '/a.md');
66
+ }
67
+
68
+ // extract_images: priority names first, fingerprinted copies and skipped dirs excluded.
69
+ {
70
+ const repo = fs.mkdtempSync(path.join(tmp, 'repo-'));
71
+ const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64');
72
+ fs.mkdirSync(path.join(repo, 'public'));
73
+ fs.mkdirSync(path.join(repo, 'node_modules'));
74
+ fs.writeFileSync(path.join(repo, 'public', 'photo.png'), Buffer.concat([png, Buffer.alloc(10)]));
75
+ fs.writeFileSync(path.join(repo, 'public', 'logo.png'), Buffer.concat([png, Buffer.alloc(50)]));
76
+ fs.writeFileSync(path.join(repo, 'public', `logo-${'a'.repeat(21)}-${'b'.repeat(21)}.png`), png);
77
+ fs.writeFileSync(path.join(repo, 'node_modules', 'dep.png'), png);
78
+ const found = walk(repo);
79
+ assert.deepStrictEqual(found.map(item => item.relPath), [path.join('public', 'logo.png'), path.join('public', 'photo.png')]);
80
+ assert.match(signature(found), /^[0-9a-f]{64}$/);
81
+
82
+ const manifest = path.join(tmp, 'images.json');
83
+ assert.strictEqual(run('extract_images.js', [repo, manifest, '--cache']).status, 0);
84
+ const images = JSON.parse(fs.readFileSync(manifest, 'utf8'));
85
+ assert.strictEqual(images.count, 2);
86
+ assert(images.images['/assets/logo.png'].startsWith('data:image/png;base64,'));
87
+ assert.match(run('extract_images.js', [repo, manifest, '--cache']).stdout, /cache hit \(2 images\)/);
88
+ }
89
+
90
+ // inject_assets: head block, fonts and bridges.
91
+ {
92
+ assert.strictEqual(buildCssHeadBlock(null, null), '<script src="https://cdn.tailwindcss.com"></script>');
93
+ const head = buildCssHeadBlock(null, {
94
+ framework: 'bootstrap', compiled_css_path: 'branding.css',
95
+ google_fonts: [{ family: 'Plus Jakarta Sans', weights: ['500', '700'] }, 'Inter'],
96
+ default_colors: { primary: '#123456', text_muted: '#999' },
97
+ });
98
+ assert(head.startsWith('<link rel="stylesheet" href="branding.css">'));
99
+ assert(head.includes('family=Plus+Jakarta+Sans:wght@500;700&display=swap'));
100
+ assert(head.includes('family=Inter&display=swap'));
101
+ assert(head.includes("--walkthrough-card-font: 'Plus Jakarta Sans'"), 'descriptor fonts name the title-card font');
102
+ assert(head.includes('--brand-text-muted: #999;'));
103
+ assert(!head.includes('cdn.tailwindcss.com'), 'compiled non-Tailwind CSS needs no CDN');
104
+ assert.strictEqual(firstGoogleFontFamily({ google_fonts: ['https://fonts.googleapis.com/css2?family=Open+Sans:wght@400'] }), 'Open Sans');
105
+ assert.strictEqual(fontDescriptorToUrl(''), null);
106
+
107
+ assert.match(desktopWindowBgBridge('body.theme-dark { color: red; background: #111; }'), /body\.theme-dark \.desktop-window-content \{ background: #111; \}/);
108
+ assert.strictEqual(desktopWindowBgBridge('.card { background: red; }'), '');
109
+ const offset = fixedOffsetBridgeCss('<html class="wp-toolbar"><body>', 'html.wp-toolbar{padding-top:32px}');
110
+ assert(offset.includes('html.wp-toolbar{padding-top:0 !important;}[data-walkthrough-stage]{padding-top:32px;}'));
111
+ assert.strictEqual(fixedOffsetBridgeCss('<html class="other">', 'html.wp-toolbar{padding-top:32px}'), '');
112
+
113
+ // Replacement text is literal: "$&" in CSS must survive.
114
+ assert.strictEqual(injectCss('<head><!-- INJECT_CSS --></head>', '<style>a[href$="&"]{}</style>'), '<head><style>a[href$="&"]{}</style></head>');
115
+ assert.strictEqual(findLogo({ 'logo-dark.png': 1, 'logo.svg': 2, 'favicon-logo.png': 3, 'brandmark.png': 4 }), 'logo.svg');
116
+ }
117
+
118
+ // inject_assets end to end: idempotent injection, image tokens, walkthrough stage and cards.
119
+ {
120
+ const cwd = fs.mkdtempSync(path.join(tmp, 'project-'));
121
+ const out = path.join(cwd, 'out');
122
+ fs.mkdirSync(out);
123
+ const step = '<!doctype html><html><head><!-- INJECT_CSS --></head><body><img src="{{img:logo.png}}"><p>{{img:missing.png}}</p></body></html>';
124
+ fs.writeFileSync(path.join(out, 'step_0.html'), step);
125
+ fs.writeFileSync(path.join(out, 'actions.json'), JSON.stringify({ steps: [{ action: 'click' }] }));
126
+ fs.writeFileSync(path.join(out, 'article.json'), JSON.stringify({ title: 'Export a table', introduction: 'Save rows <fast>.', summary: '' }));
127
+ const images = path.join(cwd, 'images.json');
128
+ fs.writeFileSync(images, JSON.stringify({ images: { 'public/logo.svg': 'data:image/svg+xml;base64,PHN2Zy8+', 'logo.png': 'data:image/png;base64,AAAA' } }));
129
+
130
+ const first = run('inject_assets.js', [out, '', images, '--walkthrough', path.join(out, 'actions.json')], { cwd });
131
+ assert.strictEqual(first.status, 0, first.stderr);
132
+ const html = fs.readFileSync(path.join(out, 'step_0.html'), 'utf8');
133
+ assert(html.includes('<!-- RTFM_CSS_INJECTED -->'));
134
+ assert(html.includes('src="data:image/png;base64,AAAA"'));
135
+ assert(html.includes('{{img:missing.png}}'), 'unknown images are left for the lint to report');
136
+ assert(html.includes('<div data-walkthrough-stage="1"'));
137
+ assert(html.includes('data-walkthrough-wipe-in="1"'));
138
+ assert.strictEqual(fs.readFileSync(path.join(out, 'step_0.html.pre'), 'utf8'), step);
139
+ const intro = fs.readFileSync(path.join(out, 'step_intro.html'), 'utf8');
140
+ assert(intro.includes('Save rows &lt;fast&gt;.'));
141
+ assert(intro.includes('src="data:image/png;base64,AAAA"'), 'the repo logo is embedded');
142
+ assert(!fs.readFileSync(path.join(out, 'step_outro.html'), 'utf8').includes('walkthrough-card-tagline"'), 'an empty summary has no tagline');
143
+
144
+ // Re-running on injected HTML must not stack a second CSS block.
145
+ assert.strictEqual(run('inject_assets.js', [out, '', images], { cwd }).status, 0);
146
+ assert.strictEqual(fs.readFileSync(path.join(out, 'step_0.html'), 'utf8').split('<!-- RTFM_CSS_INJECTED -->').length, 2);
147
+ assert.strictEqual(run('inject_assets.js', [out, ''], { cwd }).status, 2);
148
+ assert.strictEqual(run('inject_assets.js', [path.join(cwd, 'nope'), '', images], { cwd }).status, 1);
149
+ }
150
+
151
+ fs.rmSync(tmp, { recursive: true, force: true });
152
+ console.log('node port tests passed');