|
| 1 | +--- |
| 2 | +name: update-web-assets |
| 3 | +description: > |
| 4 | + Update the third-party JavaScript vendored into the app for the artifact, |
| 5 | + diagram, and math WebViews (KaTeX, mermaid, marked, highlight.js, Tailwind, |
| 6 | + Babel, React). Checks what is outdated, reads each pin's rationale before |
| 7 | + proposing a bump, re-downloads from the official source, verifies the lock, |
| 8 | + and runs the gates. Ends by naming what must be checked on a device, because |
| 9 | + a broken WebView asset fails silently. Use when bumping a vendored web |
| 10 | + library, adding a new one, or investigating whether a rendering bug comes |
| 11 | + from a stale pin. |
| 12 | +allowed-tools: Bash, Read, Edit, Write, Glob, Grep, WebFetch, AskUserQuestion |
| 13 | +argument-hint: "[asset-id ...] (optional, defaults to checking all of them)" |
| 14 | +--- |
| 15 | + |
| 16 | +# Update vendored web assets |
| 17 | + |
| 18 | +The artifact, diagram and math renderers are WebViews, and every script they execute ships |
| 19 | +inside the app. `scripts/web-assets.json` is the registry; `scripts/vendor-web-assets.py` |
| 20 | +moves the bytes; `scripts/web-assets.lock.json` records a sha256 per file and CI verifies it. |
| 21 | + |
| 22 | +**Read `scripts/web-assets.json` before doing anything.** Every entry carries a |
| 23 | +`pin_reason`, and several of them are load-bearing rather than informational — two pins |
| 24 | +must NOT be moved to the newest version, and the reason is in the file, not in this skill. |
| 25 | + |
| 26 | +## What makes this different from a Gradle dependency bump |
| 27 | + |
| 28 | +A wrong version here does not fail the build, fail a test, or throw anything Kotlin can |
| 29 | +catch. The failure happens inside the WebView, and the usual shape of it is a blank box or |
| 30 | +a feature that quietly stops working. Both libraries this system replaced were already |
| 31 | +broken that way before anyone noticed: |
| 32 | + |
| 33 | +- **marked** deleted its `highlight` option in v5. Passing one to `setOptions` is accepted |
| 34 | + and silently ignored, so syntax highlighting had been dead with no error anywhere. |
| 35 | +- **highlight.js** was being loaded from a URL that served CommonJS. In a browser |
| 36 | + `<script>` that throws immediately and never defines `hljs`. |
| 37 | + |
| 38 | +So: the gates below prove the wiring is *consistent*, and only a device proves it *renders*. |
| 39 | +Do not report a bump as done on green gates alone. |
| 40 | + |
| 41 | +## Phase 0 — Establish what is stale |
| 42 | + |
| 43 | +```bash |
| 44 | +scripts/vendor-web-assets.py --check # tree matches the lock (should be clean first) |
| 45 | +scripts/vendor-web-assets.py --outdated # what npm has that we don't |
| 46 | +scripts/vendor-web-assets.py --list # what each asset is for, and who uses it |
| 47 | +``` |
| 48 | + |
| 49 | +`--outdated` prints each pin's `pin_reason` next to any version it reports. Read it. It is |
| 50 | +advisory output about a registry that has opinions. |
| 51 | + |
| 52 | +If `--check` fails before you have changed anything, stop and report it — someone |
| 53 | +hand-edited a vendored file, or a repin was left half-applied. That is a finding to raise, |
| 54 | +not a precondition to quietly re-sync away. |
| 55 | + |
| 56 | +## Phase 1 — Decide, per asset |
| 57 | + |
| 58 | +For each asset the user named (or each one `--outdated` flags, if they named none): |
| 59 | + |
| 60 | +1. Read its `pin_reason` in the registry. |
| 61 | +2. If the reason forbids or constrains the bump, say so and **do not** bump it. Two known |
| 62 | + standing constraints, both explained in full in the registry: |
| 63 | + - **mermaid** must stay on the v10 line (v11+ is ESM-only and fails in Android WebView). |
| 64 | + - **react** / **react-dom** must move together and are bounded by UMD availability. |
| 65 | +3. Check the upstream changelog for breaking changes in the range, especially anything |
| 66 | + touching the entry point path the registry's `files` block names. A package that |
| 67 | + reorganises its `dist/` is the most likely way a bump fails, and `--sync` will refuse |
| 68 | + with a clear error if a declared path no longer exists. |
| 69 | +4. For a major version, use AskUserQuestion rather than deciding alone. |
| 70 | + |
| 71 | +`tailwind` is pinned to a URL, not npm, because Tailwind v3 never published a browser |
| 72 | +build. `--bump` refuses it deliberately; repinning it means editing both the version and |
| 73 | +every file URL in the registry by hand, and moving to `@tailwindcss/browser` means moving |
| 74 | +to v4, which is a behaviour change for artifacts (the registry explains which utilities |
| 75 | +changed). |
| 76 | + |
| 77 | +## Phase 2 — Apply |
| 78 | + |
| 79 | +```bash |
| 80 | +scripts/vendor-web-assets.py --bump <id> <version> |
| 81 | +``` |
| 82 | + |
| 83 | +This repins the registry, wipes that asset's directory, re-downloads from the official |
| 84 | +tarball, rewrites the lock, and regenerates `VendoredWebAssets.kt`. The wipe is deliberate: |
| 85 | +a repin that drops a file must not leave the old one behind to be served. |
| 86 | + |
| 87 | +Adding a **new** asset instead: add a registry entry (with a real `why`, `pin_reason` and |
| 88 | +`used_by`), run `--sync`, then reference it from the renderer by a path relative to the |
| 89 | +document base — never an absolute URL. |
| 90 | + |
| 91 | +If the bump changes the entry point path or an API the renderer calls, update the HTML |
| 92 | +builder in the same pass. The builders are: |
| 93 | + |
| 94 | +| Asset | Built in | |
| 95 | +|---|---| |
| 96 | +| katex | `androidMain/components/LatexBlock.kt`, `iosMain/components/PlatformMediaComponents.ios.kt` | |
| 97 | +| mermaid | `androidMain/components/MermaidDiagram.kt`, `commonMain/…/artifact/MermaidWebContent.kt`, `iosMain/components/PlatformMediaComponents.ios.kt` | |
| 98 | +| marked, marked-highlight, highlight | `commonMain/…/artifact/MarkdownWebContent.kt` | |
| 99 | +| tailwind, babel, react, react-dom | `commonMain/…/artifact/ArtifactWebContent.kt` | |
| 100 | + |
| 101 | +Note KaTeX and mermaid each have **two** independent HTML builders (Android and iOS) that |
| 102 | +do not share code. Changing one and not the other is the easiest mistake to make here. |
| 103 | + |
| 104 | +## Phase 3 — Gates |
| 105 | + |
| 106 | +```bash |
| 107 | +scripts/vendor-web-assets.py --check |
| 108 | +./gradlew :feature:chat:testDebugUnitTest --tests '*VendoredAssetReferenceTest*' \ |
| 109 | + --tests '*ReactArtifactRenderTest*' |
| 110 | +./gradlew :feature:chat:detekt :feature:chat:detektMetadataCommonMain |
| 111 | +./gradlew :app:assembleDebug |
| 112 | +``` |
| 113 | + |
| 114 | +`VendoredAssetReferenceTest` is the one that matters most: it asserts that no document |
| 115 | +references a remote origin, that no CSP lets one execute, and that **every path a document |
| 116 | +references exists in the generated manifest** — which is what catches a bump that renamed a |
| 117 | +dist file, since the symptom otherwise is a blank WebView. |
| 118 | + |
| 119 | +Per the repo's workflow preference, skip iOS builds unless asked; if asked, stop at the |
| 120 | +Gradle framework link (`./gradlew :feature:chat:compileKotlinIosSimulatorArm64`). |
| 121 | + |
| 122 | +## Phase 4 — Report, and name the device check |
| 123 | + |
| 124 | +Report: which assets moved, from and to; the APK size delta if it is material; anything in |
| 125 | +a `pin_reason` you deliberately did not act on. |
| 126 | + |
| 127 | +Then state plainly that the gates cannot confirm rendering, and name what to look at for |
| 128 | +the assets that actually moved: |
| 129 | + |
| 130 | +| Asset | What to look at on a device | |
| 131 | +|---|---| |
| 132 | +| katex | a message containing `$$x^2$$` — check glyphs AND that fonts loaded (no fallback serif) | |
| 133 | +| mermaid | a ```mermaid block in a message, and a mermaid artifact opened fullscreen | |
| 134 | +| marked / marked-highlight / highlight | a markdown artifact with a fenced code block — highlighting present, theme matches light/dark | |
| 135 | +| tailwind | an HTML artifact using utility classes | |
| 136 | +| babel / react / react-dom | a React artifact that uses hooks; then one importing an unbundled package, which must show the naming error rather than a blank box | |
| 137 | + |
| 138 | +Do not open a PR. Per the repo's convention, stop at local commits and let the user device-test first. |
0 commit comments