<><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></>, <><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z" /></>, <><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /></>, <><circle cx="12" cy="12" r="9" /><circle cx="12" cy="12" r="1" fill="currentColor" /></>, <><circle cx="12" cy="12" r="9" /><circle cx="12" cy="12" r="4" /><circle cx="12" cy="12" r="1" /></>, <><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /></>, <><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" /></>, <><circle cx="12" cy="5" r="2.5" /><circle cx="5" cy="19" r="2.5" /><circle cx="19" cy="19" r="2.5" /><path d="M12 7.5l-6.5 9M12 7.5l6.5 9M7.5 19h9" /></>, <><circle cx="18" cy="5" r="3" /><circle cx="6" cy="12" r="3" /><circle cx="18" cy="19" r="3" /><path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4" /></>, <><circle cx="5" cy="12" r="1.5" fill="currentColor" /><circle cx="12" cy="12" r="1.5" fill="currentColor" /><circle cx="19" cy="12" r="1.5" fill="currentColor" /></>, <><circle cx="7.5" cy="15.5" r="3.5" /><path d="M10 13l9-9M16 7l3 3M14 9l3 3" /></>, <><circle cx="8" cy="15" r="4" /><path d="M10.85 12.15L19 4" /><path d="M18 5l3 3" /><path d="M15 8l3 3" /></>, <><circle cx="9" cy="8" r="4" /><path d="M2 21a7 7 0 0 1 14 0" /><circle cx="17" cy="6" r="3" /><path d="M22 18a5 5 0 0 0-7-4.5" /></>, <><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z" /><circle cx="12" cy="12" r="3" /></>, <><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 1 0-7-7l-1 1" /><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 1 0 7 7l1-1" /></>, <><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" /></>, <><path d="M12 16V4" /><path d="M6 10l6-6 6 6" /><path d="M4 20h16" /></>, <><path d="M12 2L2 7l10 5 10-5-10-5z" /><path d="M2 17l10 5 10-5M2 12l10 5 10-5" /></>, <><path d="M12 4v12M6 10l6 6 6-6" /><path d="M4 20h16" /></>, <><path d="M12 5v14M5 12h14" /></>, <><path d="M12 9v4M12 17h.01" /><path d="M10.3 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /></>, <><path d="M14.06 2.94l7 7-11 11H3v-7.06l11.06-10.94z" /><path d="M13 4l7 7" /></>, <><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><path d="M15 3h6v6M10 14L21 3" /></>, <><path d="M18.36 6.64a9 9 0 1 1-12.73 0" /><path d="M12 2v10" /></>, <><path d="M2 20h.01M7 20v-4M12 20v-8M17 20V8M22 20V4" /></>, <><path d="M21 12a9 9 0 0 1-15 6.7L3 16" /><path d="M3 12a9 9 0 0 1 15-6.7L21 8" /><path d="M21 3v5h-5M3 21v-5h5" /></>, <><path d="M3 12v-2a2 2 0 0 1 2-2h2l5-4v16l-5-4H5a2 2 0 0 1-2-2z" /><path d="M16 8a5 5 0 0 1 0 8M19 5a9 9 0 0 1 0 14" /></>, <><path d="M3 6h13M3 12h9M3 18h5" /><path d="M17 14l3 3 3-3M20 9v8" /></>, <><path d="M3 6h18" /><path d="M3 12h18" /><path d="M3 18h12" /></>, <><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /></>, <><path d="M3 7l9-4 9 4M3 7v10l9 4 9-4V7M3 7l9 4 9-4M12 11v10" /></>, <><path d="M4 6h11M19 6h1M4 12h2M10 12h10M4 18h7M15 18h5" /><circle cx="17" cy="6" r="2" /><circle cx="8" cy="12" r="2" /><circle cx="13" cy="18" r="2" /></>, <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 8 3 8H3s3-1 3-8z" /><path d="M10 21a2 2 0 0 0 4 0" /></>, <><path d="M8 6h13M8 12h13M8 18h13" /><circle cx="4" cy="6" r="1" fill="currentColor" /><circle cx="4" cy="12" r="1" fill="currentColor" /><circle cx="4" cy="18" r="1" fill="currentColor" /></>, <><rect x="2" y="3" width="20" height="14" rx="2" /><path d="M8 21h8M12 17v4" /></>, <><rect x="2" y="6" width="14" height="12" rx="2" /><path d="M22 8l-6 4 6 4V8z" /></>, <><rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="9" cy="9" r="2" /><path d="M21 15l-5-5L5 21" /></>, <><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M3 7h4M3 11h4M3 15h4M3 19h4M17 7h4M17 11h4M17 15h4M17 19h4M7 3v18M17 3v18" /></>, <><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M3 9h18M9 21V9" /></>, <><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M9 12l3-3 3 3M12 9v8" /></>, <><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M9 3v18" /></>, <><rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /></>, <><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></>, <><rect x="3" y="6" width="18" height="12" rx="1" /><path d="M3 10h18" /><circle cx="7" cy="14" r="1" fill="currentColor" /><circle cx="11" cy="14" r="1" fill="currentColor" /></>, <><rect x="3" y="7" width="18" height="10" rx="1" /><rect x="6" y="10" width="4" height="4" /><rect x="14" y="10" width="4" height="4" /><path d="M3 11H1M3 13H1M23 11h-2M23 13h-2" /></>, <><rect x="4" y="4" width="16" height="16" rx="2" /><rect x="9" y="9" width="6" height="6" /><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3" /></>, <><rect x="5" y="11" width="14" height="10" rx="2" /><path d="M8 11V7a4 4 0 0 1 8 0v4" /></>, <><rect x="6" y="4" width="4" height="16" /><rect x="14" y="4" width="4" height="16" /></>, <><rect x="9" y="9" width="13" height="13" rx="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></>, <path d="M12 2l10 10-10 10L2 12 12 2z" />, <path d="M13 2L3 14h8l-1 8 10-12h-8l1-8z" fill="currentColor" stroke="none" />, <path d="M19 12H5M11 5l-7 7 7 7" />, <path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.4 8.4 0 0 1 3.8-.9h.5a8.5 8.5 0 0 1 8 8v.5z" />, <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />, <path d="M3 11l9-8 9 8M5 9.5V21h14V9.5" />, <path d="M3 5h18l-7 9v6l-4-2v-4L3 5z" />, <path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z" />, <path d="M5 12h14M13 5l7 7-7 7" />, <path d="M5 12l5 5L20 7" />, <path d="M6 4l14 8-14 8V4z" fill="currentColor" stroke="none" />, <path d="M6 6l12 12M6 18L18 6" />, <path d="M6 9l6 6 6-6" />, <path d="M9 6l6 6-6 6" />,


  if (!path) return null;
  return (
    <svg
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.5"
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
      style={style}
    >
      {path}
    </svg>
  );
}

window.Icon = Icon;


Let me check the entire icon registry more clearly:


Bash

grep -oP "^\s*\K\w+(?=:)" /tmp/wd/wild-dragon/services/web-ui/public/icons.jsx | sort


alert
arrowLeft
arrowRight
audio
bell
capture
check
chevron
chevronDown
clock
cluster
comment
container
copy
cpu
diamond
download
edit
editor
externalLink
eye
film
filter
folder
globe
gpu
grid
hdd
home
image
jobs
key
layers
layout
library
link
list
lock
monitor
moon
more
package
panel
pause
play
plus
power
proxy
record
refresh
search
settings
share
signal
sliders
sort
sun
token
trash
upload
users
video
x
zap


grep -oh 'Icon name="[^"]*"' /tmp/wd/wild-dragon/services/web-ui/public/screens-*.jsx | sed 's/.*name="\([^"]*\)".*/\1/' | sort -u


alert
arrowLeft
arrowRight
audio
check
clock
cluster
cpu
download
edit
folder
gpu
grid
hdd
jobs
key
layout
library
list
lock
more
play
plus
record
refresh
search
sliders
trash
upload
video
x


grep -n "padStart.*2.*'00'" /tmp/wd/wild-dragon/services/web-ui/public/screens-ingest.jsx


243:              String(Math.floor((s % 3600) / 60)).padStart(2, '00') + ':' +


Found one! Let me verify this is actually a bug:


240	  if (r.status === 'recording' && r.started_at) {
241	    const s = Math.floor((Date.now() - new Date(r.started_at)) / 1000);
242	    elapsed = String(Math.floor(s / 3600)).padStart(2, '0') + ':' +
243	              String(Math.floor((s % 3600) / 60)).padStart(2, '00') + ':' +
244	              String(s % 60).padStart(2, '0');
245	  }
246	  const cfg = r.source_config || {};
247	  return {
248	    ...r,
249	    source: r.source_type || '—',


grep -n "\.padStart" /tmp/wd/wild-dragon/services/web-ui/public/screens-ingest.jsx


242:    elapsed = String(Math.floor(s / 3600)).padStart(2, '0') + ':' +
243:              String(Math.floor((s % 3600) / 60)).padStart(2, '00') + ':' +
244:              String(s % 60).padStart(2, '0');
340:      return String(Math.floor(d / 3600)).padStart(2, '0') + ':' +
341:             String(Math.floor((d % 3600) / 60)).padStart(2, '0') + ':' +
342:             String(d % 60).padStart(2, '0');


grep -n "\.padStart" /tmp/wd/wild-dragon/services/web-ui/public/data.jsx


32:  if (h > 0) return h + ':' + String(m).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
33:  return m + ':' + String(sec).padStart(2, '0');
77:    elapsed = String(Math.floor(s / 3600)).padStart(2, '0') + ':' +
78:              String(Math.floor((s % 3600) / 60)).padStart(2, '0') + ':' +
79:              String(s % 60).padStart(2, '0');


Perfect! Now let me compile the findings. Let me also check for any remaining potential issues with route ordering:


grep -n "router.post.*/:id\|router.post.*'/" /tmp/wd/wild-dragon/services/mam-api/src/routes/assets.js | head -10


97:router.post('/', async (req, res, next) => {
207:router.post('/cleanup-live', async (req, res, next) => {
268:router.post('/:id/copy', async (req, res, next) => {
308:router.post('/:id/mark-empty', async (req, res, next) => {
328:router.post('/:id/generate-proxy', async (req, res, next) => {
348:router.post('/backfill-proxies', async (_req, res, next) => {
375:router.post('/:id/retry', async (req, res, next) => {


grep -n "normalizeRecorder\|_normRecorder" /tmp/wd/wild-dragon/services/web-ui/public/screens-ingest.jsx | head -5


238:function _normRecorder(r) {
267:        const norm = (raw || []).map(_normRecorder);
531:          const norm = (raw || []).map(_normRecorder);


grep -n "function.*normalizeRecorder\|const.*normalizeRecorder" /tmp/wd/wild-dragon/services/web-ui/public/*.jsx


/tmp/wd/wild-dragon/services/web-ui/public/data.jsx:73:function normalizeRecorder(r) {


Let me check for the critical issue with normalizeRecorder duplication:


262	  const [recorders, setRecorders] = React.useState(window.ZAMPP_DATA.RECORDERS);
263
264	  const refresh = React.useCallback(() => {
265	    window.ZAMPP_API.fetch('/recorders')
266	      .then(raw => {
267	        const norm = (raw || []).map(_normRecorder);
268	        window.ZAMPP_DATA.RECORDERS = norm;
269	        setRecorders(norm);
270	      })
271	      .catch(() => {});
272	  }, []);
273
274	  React.useEffect(() => {
275	    refresh();
276	    const id = setInterval(refresh, 10000);
277	    return () => clearInterval(id);
278	  }, []);
279
280	  const liveCount = recorders.filter(r => r.status === 'recording').length;
281	  const errCount  = recorders.filter(r => r.status === 'error').length;
282
283	  return (
284

285

286
Recorders

287	        Live ingest from SRT, RTMP, and SDI sources
288

289	        {(liveCount > 0 || errCount > 0) && (
290

291
292	            {liveCount > 0 ? liveCount + ' recording' : ''}{errCount > 0 ? (liveCount > 0 ? ' · ' : '') + errCount + ' error' : ''}
293

294	        )}
295	        <button className="btn ghost sm" onClick={refresh}><Icon name="refresh" />Refresh</button>
296	        <button className="btn primary" onClick={onNew}><Icon name="plus" />New recorder</button>


grep -A10 "const refresh = React.useCallback" /tmp/wd/wild-dragon/services/web-ui/public/screens-ingest.jsx | head -15


  const refresh = React.useCallback(() => {
    window.ZAMPP_API.fetch('/recorders')
      .then(raw => {
        const norm = (raw || []).map(_normRecorder);
        window.ZAMPP_DATA.RECORDERS = norm;
        setRecorders(norm);
      })
      .catch(() => {});
  }, []);

  React.useEffect(() => {


Perfect. Now let me compile the comprehensive audit findings. Based on my analysis:

## AUDIT FINDINGS: Pass-3

**1. screens-ingest.jsx:243 - Typo in padStart argument**
File: `/tmp/wd/wild-dragon/services/web-ui/public/screens-ingest.jsx:243`
Issue: `padStart(2, '00')` should be `padStart(2, '0')`. The second argument is the fill character (single char), not a string. Inconsistent with lines 242 and 244 and matching code in data.jsx:77-79.
Fix: Change `padStart(2, '00')` to `padStart(2, '0')`

**2. screens-ingest.jsx:322 - Missing dependency in useEffect**
File: `/tmp/wd/wild-dragon/services/web-ui/public/screens-ingest.jsx:322`
Issue: `React.useEffect(() => { setRecorder(initialRecorder); }, [initialRecorder.id, initialRecorder.status])` depends on object properties but should depend on the object itself. If `initialRecorder` object reference changes, the effect won't fire even though properties change.
Fix: Add `initialRecorder` to dependency array or restructure logic

**3. assets.js:348 - POST route ordering (shadowing risk)**
File: `/tmp/wd/wild-dragon/services/mam-api/src/routes/assets.js:348`
Issue: `/backfill-proxies` POST route at line 348 appears after other POST routes but before `/:id/retry` at 375. Express matches routes in order, so ensure named routes precede parameterized routes to prevent shadowing. Current order is acceptable but borders on fragile.
Fix: Consolidate all POST routes with no parameters before POST routes with `/:id` pattern (already mostly correct; maintain discipline)

**4. screens-projects.jsx:46 - Potential missing default handling**
File: `/tmp/wd/wild-dragon/services/web-ui/public/screens-projects.jsx:46`
Issue: `window.ZAMPP_DATA.PROJECTS` initialization at mount, but PROJECTS is an array. If hydration timing fails or window.ZAMPP_DATA is undefined, `useState` will fail. No guard clause.
Fix: Add guard: `const [projects, setProjects] = React.useState(window.ZAMPP_DATA?.PROJECTS || []);`

**5. screens-library.jsx:89 - Conditional function reference (binIcon)**
File: `/tmp/wd/wild-dragon/services/web-ui/public/screens-library.jsx:89`
Issue: Uses `binIcon(b.icon)` but function is defined at line 350 of the same file. Function exists but late-stage definition. No actual bug, but unusual pattern for a utility.
Fix: Consider moving `binIcon` definition earlier or extracting to a utilities module for clarity

**6. Icon coverage verification**
All `Icon name="..."` references in screens match available icons in icons.jsx. No missing icon references detected across screens-library, screens-asset, screens-projects, screens-jobs, screens-admin, screens-ingest.

**7. screens-asset.jsx - Missing null check on stream URLs**
File: `/tmp/wd/wild-dragon/services/web-ui/public/screens-asset.jsx` (streaming section)
Issue: Video streaming endpoints (`/stream`, `/live-path`, `/video`) are called without validating asset.id exists or API availability. If asset fetch fails mid-play, player will request invalid URLs.
Fix: Add `if (!asset?.id) return <div>Asset not available</div>;` before player initialization

**Character count: ~350 words, 7 findings (under 25 cap)**