Skip to content

Icon pipeline → public/, delete static/, and run vitest in CI - #236

Merged
ralyodio merged 3 commits into
masterfrom
worktree-icon-pipeline-and-ci
Aug 11, 2026
Merged

Icon pipeline → public/, delete static/, and run vitest in CI#236
ralyodio merged 3 commits into
masterfrom
worktree-icon-pipeline-and-ci

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Follow-ups to #235. Three commits, reviewable separately.

1. fix(icons): point the icon pipeline at public/

This is the one that actually mattered, and it's why I didn't just delete static/ as I'd originally suggested — static/ was still live:

  • pnpm icons:generate wrote into static/icons
  • pnpm icons:install copied out of static/icons

But Next only serves public/. So regenerating icons updated a tree nobody serves while the served one silently went stale — which is how the two directories drifted apart to begin with, and a live footgun for the PWA icons fixed in #235.

Both scripts now read and write public/. The generator also emits every icon the app references, not a subset: previously it produced the apple-touch icons and four icon-* sizes while leaving the android-chrome-* family — the ones manifest.json actually points at — untouched. Added those plus the favicon-NxN set and the Windows mstile tiles (including the non-square 310x150).

Running it now rewrites exactly the 38 PNGs already in public/icons and creates no new files, so script and tree agree. No regenerated binaries are committed — this is tooling only, no artwork changes.

Also dropped the needsSolidBackground branch: background only paints the letterbox fit: 'contain' adds, and favicon.svg is square, so it painted nothing. The committed icons have always had transparent pixels. Making the manifest's maskable 192/512 icons genuinely opaque needs .flatten() plus safe-zone padding, which changes how the installed icon looks — a design decision, not something to fold in silently.

Five new tests tie the generator to the manifest and layout: every icon either references must be one the generator emits, and no tooling may point at static/ again. That's what caught the missing mstile tiles.

2. chore: delete static/

Now safe, since nothing references it. Verified every one of the 56 files has a public/ counterpart — including .well-known/security.txt, which prod serves 200.

Also fixed: public/qryptchat.desktop hardcoded Icon=/home/ettinger/src/qrypt.chat/qryptochat-web/static/qryptchat.png — an absolute path into a developer's home directory, with a typo in the folder name, so the installed desktop launcher had no icon for anybody else. The installer registers the icon in the hicolor theme, so the entry just needs Icon=qryptchat.

3. ci: run vitest

CI ran CodeQL, Semgrep, npm-audit and gitleaks but never vitest — which is how the broken vitest.config.js (fixed in #235) went unnoticed.

I triaged all 139 test files in isolation: 72 passing, 66 failing, 1 hanging. Two mechanical causes covered a third of it:

  • 24 files did import { describe, it } from 'mocha' while the repo runs vitest — those bindings come back undefined and the file dies on Cannot read properties of undefined (reading 'describe'). Removed the import (globals: true is already set) and renamed mocha's before/after to beforeAll/afterAll.
  • jsdom ships no IndexedDB, but the app stores private keys there, so every test touching key storage hit ReferenceError: indexedDB is not defined. tests/setup.js now imports fake-indexeddb/auto.

That got it to 80 passing / 58 failing / 1 hanging. The remaining 59 are listed in tests/quarantine.js and excluded from the default suite:

cause files
failing assertions 19
dead imports (SvelteKit +server.js, $lib/*) 13
needs live Supabase 12
other 9
undefined globals (e.g. sinon) 5
parse error (top-level await) 1

Quarantined rather than deleted: each still documents behaviour worth porting, and a permanently red build gets ignored. pnpm test:quarantined runs just those so the backlog can be worked off; deleting a line is how a file rejoins CI. tests/QUARANTINE.md has the breakdown and the fix for each category.

CI runs pnpm test:ci: 80 files, 469 tests, ~33s, green.

Note tests/pwa-session-integration.test.js hangs rather than fails — that's what made pnpm test unusable, and worth fixing early.

Verification

  • pnpm test:ci — 80 files / 469 tests passing
  • pnpm test:quarantined — runs the 59, fails as expected
  • node scripts/generate-icons.js — 38 PNGs into public/icons, no new files
  • Every static/ file confirmed to exist in public/ before deletion

🤖 Generated with Claude Code

ralyodio and others added 3 commits August 10, 2026 18:07
`pnpm icons:generate` wrote into `static/icons` and `pnpm icons:install` read
from there, but Next only serves `public/`. Regenerating icons updated a tree
nobody serves while the served one silently went stale — which is how the two
directories drifted apart in the first place.

- generate-icons.js reads ./public/favicon.svg and writes ./public/icons.
- It now emits every icon the app references, not a subset: the android-chrome-*
  family (which is what public/manifest.json actually points at), the favicon-NxN
  set, and the Windows mstile tiles including the non-square 310x150. Previously
  regenerating refreshed the apple-touch icons and four icon-* sizes while
  leaving the icons Chromium installs with untouched.
- install-desktop-icons.sh copies from public/. Its 16px and 32px fallbacks
  looked for static/favicon-{16,32}.png, which never existed, so both branches
  were dead; they now use public/icons/favicon-{16x16,32x32}.png.
- Dropped the `needsSolidBackground` branch. `background` only paints the
  letterbox that `fit: 'contain'` adds, and favicon.svg is square, so it painted
  nothing — the committed icons have always had transparent pixels. Making the
  manifest's maskable 192/512 icons genuinely opaque needs `.flatten()` plus
  safe-zone padding, which changes how the installed icon looks, so it is left
  as a deliberate design decision rather than folded in here.

Running the generator now rewrites exactly the 38 PNGs already in public/icons
and creates no new files, so the script and the tree agree. Regenerated binaries
are not committed — this change is tooling only, no icon artwork changes.

tests/pwa-installability.test.js grows five checks tying the generator to the
manifest and layout: every icon either references must be one the generator
emits, and no tooling may point at static/ again. That is what caught the
missing mstile tiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
static/ was a near-exact copy of public/ left behind by the Next.js migration.
Next only serves public/, so every one of these 56 files was dead weight — and
a trap, since editing static/manifest.json looks like it should do something.

Verified before removing: every file under static/ has a counterpart in public/,
including .well-known/security.txt (which prod serves 200). The icon tooling
that genuinely did read static/ was retargeted at public/ in the previous
commit, so nothing references it any more.

Also fixed while here:
- public/qryptchat.desktop hardcoded
  `Icon=/home/ettinger/src/qrypt.chat/qryptochat-web/static/qryptchat.png`, an
  absolute path into a developer's home directory (with a typo in the folder
  name), so the installed desktop launcher had no icon for anyone else. The
  installer registers the icon in the hicolor theme, so the entry just needs the
  theme name: `Icon=qryptchat`.
- PWA_VIDEO_PLAYBACK_GUIDE.md told readers to edit static/manifest.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI ran CodeQL, Semgrep, npm-audit and gitleaks but never vitest, so nobody
noticed the suite could not start at all: vitest.config.js failed to load
because @vitejs/plugin-react@6 peers vite ^8 while vitest 4 ships vite 7. That
was fixed in #235; this wires the suite into CI so it stays runnable.

Triaging all 139 test files in isolation gave 72 passing, 66 failing and 1
hanging. Two mechanical problems accounted for a third of the breakage:

- 24 files did `import { describe, it } from 'mocha'` while the repo runs
  vitest, so those bindings were undefined and the file died on
  "Cannot read properties of undefined (reading 'describe')". Removed the
  import (vitest already sets globals: true) and renamed mocha's before/after
  to vitest's beforeAll/afterAll.
- jsdom ships no IndexedDB, but the app stores private keys there, so every
  test touching key storage hit "indexedDB is not defined". tests/setup.js now
  imports fake-indexeddb/auto.

That took it to 80 passing / 58 failing / 1 hanging. The remaining 59 are listed
in tests/quarantine.js and excluded from the default suite: 19 have genuinely
failing assertions, 13 import SvelteKit paths the migration deleted, 12 need a
live Supabase, 5 use undefined globals, 1 has a parse error, 9 are assorted.
tests/pwa-session-integration.test.js hangs rather than fails, which is what
made `pnpm test` unusable.

They are quarantined rather than deleted because each still documents intended
behaviour worth porting, and a permanently red build gets ignored. Work them off
with `pnpm test:quarantined`; deleting a line from the list is how a file
rejoins CI. tests/QUARANTINE.md explains the categories and the fix for each.

CI now runs `pnpm test:ci`: 80 files, 469 tests, ~33s, green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@socket-security

Copy link
Copy Markdown
Contributor

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedfake-indexeddb@​6.2.510010010084100

View full report

@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

96 finding(s)

HIGH/CRITICAL: 3 | MEDIUM: 17 | LOW: 76

Severity Rule Location
HIGH secret-generic-credential scripts/supabase-email.sh:21
HIGH secret-generic-credential scripts/supabase-twilio.sh:32
HIGH secret-generic-credential scripts/supabase-twilio.sh:47
MEDIUM manifest-install-lifecycle-script package.json:33
MEDIUM js-shell-exec-interpolation scripts/convert-routes.mjs:9
MEDIUM sql-template-interpolation scripts/fix-constraint-violation.js:74
MEDIUM sql-template-interpolation scripts/fix-constraint-violation.js:108
MEDIUM sql-template-interpolation scripts/fix-constraint-violation.js:153
MEDIUM sql-template-interpolation src/app/api/conversations/delete/route.js:22
MEDIUM sql-template-interpolation src/app/api/keys/reset/route.js:49
MEDIUM redos-nested-quantifier src/app/api/profile/update/route.js:73
MEDIUM sql-template-interpolation src/app/api/user/nuclear-delete/route.js:140
MEDIUM sql-template-interpolation src/app/api/user/nuclear-delete/route.js:174
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:66
MEDIUM js-unescaped-html-sink src/app/faq/page.jsx:57
MEDIUM js-unescaped-html-sink src/app/layout.jsx:138
MEDIUM js-unescaped-html-sink src/app/layout.jsx:142
MEDIUM js-unescaped-html-sink src/app/page.jsx:47
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:57
LOW secret-generic-credential src/app/api/auth/invite-anon/route.test.js:24
LOW secret-generic-credential src/app/api/auth/register-anon/route.test.js:32
LOW secret-jwt src/app/api/auth/upload-avatar/route.test.js:27
LOW secret-generic-credential src/app/api/auth/upload-avatar/route.test.js:27
LOW secret-generic-credential src/app/api/profile/update/route.test.js:63
LOW secret-generic-credential src/lib/websocket/middleware/auth.test.js:47
LOW secret-generic-credential src/lib/websocket/middleware/auth.test.js:64
LOW secret-generic-credential tests/auth-key-generation.test.js:109
LOW secret-generic-credential tests/auth-key-generation.test.js:110
LOW secret-generic-credential tests/auth-key-generation.test.js:157
LOW secret-generic-credential tests/auth-key-generation.test.js:158
LOW secret-generic-credential tests/auth-key-generation.test.js:207
LOW secret-generic-credential tests/auth-key-generation.test.js:208
LOW secret-generic-credential tests/auth-key-generation.test.js:250
LOW secret-generic-credential tests/auth-key-generation.test.js:251
LOW secret-generic-credential tests/auth-key-generation.test.js:293
LOW secret-generic-credential tests/auth-key-generation.test.js:294
LOW secret-generic-credential tests/auth-key-generation.test.js:340
LOW secret-generic-credential tests/auth-key-generation.test.js:341
LOW secret-generic-credential tests/auth-key-generation.test.js:381
LOW secret-generic-credential tests/auth-key-generation.test.js:382
LOW secret-generic-credential tests/chat-archive.test.js:22
LOW secret-jwt tests/debug-sms.js:10
LOW secret-generic-credential tests/gpg-private-key-export.test.js:63
LOW secret-generic-credential tests/gpg-private-key-export.test.js:64
LOW secret-generic-credential tests/nuclear-delete.test.js:19
LOW secret-generic-credential tests/nuclear-delete.test.js:89
LOW secret-generic-credential tests/nuclear-delete.test.js:97
LOW secret-generic-credential tests/password-security-fix-verification.test.js:51
LOW secret-generic-credential tests/password-security-fix-verification.test.js:75

…and 46 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 3de27df into master Aug 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant