Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
170 lines
9.9 KiB
JavaScript
170 lines
9.9 KiB
JavaScript
// Round forty-four: the phone has been wearing tauri's logo. `tauri android
|
|
// init` writes the generated project's res from its own template and never
|
|
// reads src-tauri/icons/android/ — which has carried the falcon since 50afa6a
|
|
// — and the detour build runs cargo and Gradle directly, so nothing ever
|
|
// copied it across: the two dominant colours in the packaged
|
|
// mipmap-xxxhdpi/ic_launcher.png are #24c8db and #ffc131, tauri's own brand
|
|
// cyan and amber. So: src-tauri/android/res/, tracked, carrying a real
|
|
// adaptive icon with a MONOCHROME layer (Android 13's themed icons; tauri's
|
|
// generator makes none), the legacy mipmaps under it, and the background as
|
|
// one #FF05080B colour resource rather than five identical flat PNGs; a copy
|
|
// of the whole tree from build.sh and dev.sh before Gradle, because
|
|
// gen/android is regenerated and gitignored; and verify-icon.sh, which opens
|
|
// the APK afterwards and reads the resources back out, because the way this
|
|
// loses is silent — a project rebuilt without the copy still compiles, still
|
|
// installs, and wears the old logo with nothing in the log. A debug APK is
|
|
// read by name and by byte (AGP packages that build type --no-crunch, so the
|
|
// entries ARE the files); a release APK keeps neither — it re-encodes the PNGs
|
|
// and shortens every resource path away — so that path asks the resource table
|
|
// through aapt2 and compares pixel dimensions. No frontend, no Rust, no
|
|
// Kotlin, no walk step: a launcher icon is not reachable from inside the app.
|
|
const fs = require('fs');
|
|
const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/';
|
|
/* branding/ is gitignored, so the kit is never inside a snapshot of the repo;
|
|
it lives on the real tree and is what the overlay is copied FROM. */
|
|
const kit = process.env.SS_KIT || 'F:/Coding/SoulStream/branding/soulstream-logo-kit/android/';
|
|
const scratch = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/';
|
|
const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\s+$/, '');
|
|
let p, s;
|
|
const load = (file) => { p = file; s = fs.readFileSync(root + p, 'utf8'); };
|
|
const save = () => fs.writeFileSync(root + p, s);
|
|
const once = (a) => {
|
|
const i = s.indexOf(a);
|
|
if (i < 0) throw new Error('missing in ' + p + ': ' + a.slice(0, 90));
|
|
if (s.indexOf(a, i + 1) >= 0) throw new Error('ambiguous in ' + p + ': ' + a.slice(0, 90));
|
|
return i;
|
|
};
|
|
const rep = (a, b) => { once(a); s = s.replace(a, () => b); };
|
|
const NL = String.fromCharCode(10);
|
|
|
|
/* Two shell files and a doc take `rep`s that would throw halfway through on a
|
|
second pass and leave a half-written tree. Refuse one here, before a byte
|
|
moves: the overlay directory is what this round creates and nothing else in
|
|
the repo makes it. */
|
|
if (fs.existsSync(root + 'src-tauri/android/res')) {
|
|
throw new Error('src-tauri/android/res already exists — round 44 has already run against ' + root);
|
|
}
|
|
/* and the kit is not optional: a half-copied overlay is the one failure
|
|
verify-icon.sh cannot tell apart from a missing one */
|
|
const DPI = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi'];
|
|
const sources = [];
|
|
for (const d of DPI) {
|
|
sources.push(['legacy/mipmap-' + d + '/ic_launcher.png', 'mipmap-' + d + '/ic_launcher.png']);
|
|
sources.push(['legacy/mipmap-' + d + '/ic_launcher.png', 'mipmap-' + d + '/ic_launcher_round.png']);
|
|
sources.push(['adaptive/res/mipmap-' + d + '/ic_launcher_foreground.png', 'mipmap-' + d + '/ic_launcher_foreground.png']);
|
|
sources.push(['adaptive/res/mipmap-' + d + '/ic_launcher_monochrome.png', 'mipmap-' + d + '/ic_launcher_monochrome.png']);
|
|
}
|
|
for (const pair of sources) {
|
|
if (!fs.existsSync(kit + pair[0])) {
|
|
throw new Error('the logo kit is not at ' + kit + ' (missing ' + pair[0] + ') — set SS_KIT');
|
|
}
|
|
}
|
|
/* And every text anchor, before a byte moves. Two shell files and a doc take
|
|
`rep`s; a missing anchor in the second or third would otherwise throw with
|
|
the overlay and verify-icon.sh already written, and the re-run guard then
|
|
refuses to finish what it started. Checked here, the round is all or
|
|
nothing. */
|
|
const A_COPY = 'echo "== gradle $(date +%T)"' + NL + 'cd src-tauri/gen/android';
|
|
const A_BUILD_DONE = 'ls -la "$APK"' + NL + 'echo "== done $(date +%T)"';
|
|
const A_DEV_APK = 'APK=src-tauri/gen/android/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk' + NL + 'echo "== install $(date +%T)"';
|
|
const A_DOC_ROW = '| `src-tauri/android/build.sh`, `deploy.sh` | the detour build (below) and build → install → launch → screenshot |';
|
|
const A_DOC_BYHAND = '# 2. package with Gradle, skipping the task that would redo the symlink';
|
|
const A_DOC_SECTION = '`adb logcat --pid=$(adb shell pidof com.soulstream.app)`.' + NL + NL + '## Verifying';
|
|
for (const [file, anchors] of [
|
|
['src-tauri/android/build.sh', [A_COPY, A_BUILD_DONE]],
|
|
['src-tauri/android/dev.sh', [A_COPY, A_DEV_APK]],
|
|
['ANDROID.md', [A_DOC_ROW, A_DOC_BYHAND, A_DOC_SECTION]],
|
|
]) {
|
|
load(file);
|
|
for (const a of anchors) once(a);
|
|
}
|
|
/* the five inserts too: a typo in a filename should not land a half-made
|
|
overlay either */
|
|
for (const f of ['ins-r44-verify-icon.txt', 'ins-r44-overlay-copy.txt', 'ins-r44-doc-row.txt', 'ins-r44-doc-byhand.txt', 'ins-r44-doc-section.txt']) {
|
|
if (!fs.existsSync(scratch + f)) throw new Error('missing insert: ' + scratch + f);
|
|
}
|
|
|
|
// ── the overlay: the app module's res, where gen/ cannot lose it ──────
|
|
/* The adaptive XML is NOT the kit's. The kit points every layer at
|
|
`@drawable/...` while shipping them as PNGs under `mipmap-*` with no
|
|
`drawable-*` directory at all — and the generated project still carries
|
|
Android Studio's template vectors under those exact drawable names, so two
|
|
of the three would resolve to the green robot and only the monochrome one
|
|
would fail the build. `@mipmap/` for the two image layers, `@color/` for
|
|
the background. Verified by an aapt2 compile+link over the real merged res
|
|
tree, 2026-09-14. */
|
|
const ICON_XML = [
|
|
'<?xml version="1.0" encoding="utf-8"?>',
|
|
'<!-- @mipmap, not the kit\'s @drawable: the layers ship as PNGs under',
|
|
' mipmap-*, and @drawable/ic_launcher_foreground still resolves to the',
|
|
' template robot in the generated project. The background is one colour',
|
|
' resource, so the gradient replaces one file when it is approved. -->',
|
|
'<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">',
|
|
' <background android:drawable="@color/ic_launcher_background" />',
|
|
' <foreground android:drawable="@mipmap/ic_launcher_foreground" />',
|
|
' <monochrome android:drawable="@mipmap/ic_launcher_monochrome" />',
|
|
'</adaptive-icon>',
|
|
'',
|
|
].join(NL);
|
|
/* Named after tauri's own file on purpose: src-tauri/icons/android/values/
|
|
ic_launcher_background.xml declares this same colour as #fff, and two files
|
|
in one values/ directory declaring one colour is a hard aapt2 error. Same
|
|
name, so a copy overwrites rather than collides. */
|
|
const BG_XML = [
|
|
'<?xml version="1.0" encoding="utf-8"?>',
|
|
'<resources>',
|
|
' <!-- the brand\'s night; the whole adaptive background is this -->',
|
|
' <color name="ic_launcher_background">#FF05080B</color>',
|
|
'</resources>',
|
|
'',
|
|
].join(NL);
|
|
|
|
const RES = root + 'src-tauri/android/res/';
|
|
fs.mkdirSync(RES + 'mipmap-anydpi-v26', { recursive: true });
|
|
fs.mkdirSync(RES + 'values', { recursive: true });
|
|
for (const d of DPI) fs.mkdirSync(RES + 'mipmap-' + d, { recursive: true });
|
|
fs.writeFileSync(RES + 'mipmap-anydpi-v26/ic_launcher.xml', ICON_XML);
|
|
fs.writeFileSync(RES + 'mipmap-anydpi-v26/ic_launcher_round.xml', ICON_XML);
|
|
fs.writeFileSync(RES + 'values/ic_launcher_background.xml', BG_XML);
|
|
for (const pair of sources) fs.copyFileSync(kit + pair[0], RES + pair[1]);
|
|
|
|
// ── the check, written before the scripts that call it ────────────────
|
|
fs.writeFileSync(root + 'src-tauri/android/verify-icon.sh', ins('ins-r44-verify-icon.txt') + NL);
|
|
|
|
// ── build.sh: copy before Gradle, read the APK back after ─────────────
|
|
/* The anchor is the gradle echo and the cd under it, which both scripts
|
|
share. A sibling round adding a resource changes nothing here: the copy
|
|
takes the whole tree, not a file list. */
|
|
load('src-tauri/android/build.sh');
|
|
rep(A_COPY, ins('ins-r44-overlay-copy.txt') + NL + A_COPY);
|
|
/* after the APK is named and listed, before "done" — so both build types
|
|
reach it, and the release one is told its bytes are crunched */
|
|
rep(A_BUILD_DONE,
|
|
'ls -la "$APK"' + NL +
|
|
'bash src-tauri/android/verify-icon.sh "$APK" $([ "$REL" = "1" ] && echo --crunched)' + NL +
|
|
'echo "== done $(date +%T)"');
|
|
save();
|
|
|
|
// ── dev.sh: the same two, the debug APK only ──────────────────────────
|
|
load('src-tauri/android/dev.sh');
|
|
rep(A_COPY, ins('ins-r44-overlay-copy.txt') + NL + A_COPY);
|
|
rep(A_DEV_APK,
|
|
'APK=src-tauri/gen/android/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk' + NL +
|
|
'bash src-tauri/android/verify-icon.sh "$APK"' + NL +
|
|
'echo "== install $(date +%T)"');
|
|
save();
|
|
|
|
// ── the doc ───────────────────────────────────────────────────────────
|
|
load('ANDROID.md');
|
|
rep(A_DOC_ROW, A_DOC_ROW + NL + ins('ins-r44-doc-row.txt'));
|
|
/* the by-hand block claims to be what the scripts do; it now misses a step */
|
|
rep(A_DOC_BYHAND, ins('ins-r44-doc-byhand.txt') + NL + A_DOC_BYHAND);
|
|
/* a section of its own, between the release build and Verifying. The anchor
|
|
is the last line of the release section plus the heading under it, so it
|
|
cannot match the identical logcat line up in "Build and run". */
|
|
rep(A_DOC_SECTION,
|
|
'`adb logcat --pid=$(adb shell pidof com.soulstream.app)`.' + NL + NL + ins('ins-r44-doc-section.txt') + NL + NL + '## Verifying');
|
|
save();
|
|
|
|
console.log('round 44 wired');
|