Commit graph

463 commits

Author SHA1 Message Date
Claude
f8eda7fc37 Merge design/overhaul-tasteskill: Flame Blue accent, home recomposition, motion layer
UI overhaul:
- Flame Blue (#1025A1) accent — official Wild Dragon brand standard
- Home: compact left-aligned header, primary ops + secondary 4-col rows
- Typography: hyphen fixes, sidebar version removed, topbar 48px
- Motion: staggered tile entry, button scale press, nav transitions
- Jobs stats: semantic card variants (active/failed/total)

🤖 Generated with Claude Code
2026-06-02 13:28:54 +00:00
4062be101d design: swap accent to Flame Blue #1025A1 (Wild Dragon brand standard)
Replace the electric amber placeholder with the official brand accent from
the Wild Dragon Forge Noir Edition identity guide (Pantone 286 C).

- --accent: #1025A1 (Flame Blue)
- --accent-hover: #1830B8 (Ember-adjacent)
- --accent-text: #C7CFEA (Halo — readable on dark surfaces, 13:1 contrast)
- Primary button text reverts to white (11.7:1 contrast on Flame Blue)
- Avatar uses Flame Blue background + white initials
- All rgba hardcodes updated to match new hue across CSS and JSX

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-02 08:56:29 -04:00
e54b8403e7 design: overhaul pass — amber accent, home recomposition, motion layer
Lever 1 (color): Replace #5B7CFA AI-blue with electric amber #E8821C across
all accent tokens, tile tones, logo glows, and hardcoded rgba values. Dark
text on amber primary buttons for WCAG AA contrast.

Lever 2 (home): Collapse centered logo hero into compact left-aligned header.
Split tile grid into primary ops row (Library, Recorders, Playout) + secondary
4-col row (Downloads, Jobs, Dashboard, Settings) with reduced visual weight.

Lever 3 (typography): Remove v1.2.0 from sidebar. Fix em-dashes to hyphens or
periods across all visible UI strings (option labels, body copy, error messages).
Topbar height 56px -> 48px.

Lever 4 (motion): Staggered entry animation for launcher tiles
(prefers-reduced-motion gated). Tactile scale(0.97) on primary/record buttons.
Smooth 150ms nav active-item transitions.

Lever 5 (blocks): Jobs stats row semantic card variants (amber glow when
active, red border when failed, quiet muted style for Total).

Lever 6 (spacing): Topbar 48px, launcher inner gap tightened, status left-aligned.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-02 08:20:15 -04:00
Claude
858c9f7b97 fix(deltacast-bridge): call VHD_SetBiDirCfg before board open + set channel SDI mode
ROOT CAUSE of 'connecting' hangs and intermittent port failures:
The DELTA-12G-e-h 8c is a bidirectional card. Without calling
VHD_SetBiDirCfg(board_index, VHD_BIDIR_80) before streaming, the
board remains in its default bi-dir config (likely 4RX/4TX) — so
RX stream opens fail with VHDERR_RESOURCEUNAVAILABLE on channels
configured as TX, causing random 'connecting' hangs per the SDK docs.

Per SDK Tools.cpp SetNbChannels() pattern:
1. Open temporary board handle
2. Check IS_BIDIR + channel counts
3. Call VHD_SetBiDirCfg(board_index, VHD_BIDIR_80) for 8ch bidir
4. Close temp handle, then open real board handle for streaming

Also add VHD_SetChannelProperty(VHD_CHANNEL_MODE_SDI) for ASI-type
channels per Sample_RX.cpp — required for 12G-ASI/3G-ASI channel
types to correctly detect incoming video standard.

🤖 Generated with Claude Code
2026-06-02 11:23:39 +00:00
Claude
59294856a2 feat(library): remove Audio tab (revisit later)
🤖 Generated with Claude Code
2026-06-02 11:18:30 +00:00
Claude
d509876444 feat(monitors): always-on tiles with last-frame freeze, elapsed timer, port badge
- MonitorTile now persists lastAssetId in local state so tiles continue
  showing content after a recording stops (frozen HLS, then thumbnail)
- When recording stops: HlsPreview stays alive briefly while segments are
  hot, then fetches /api/v1/assets/{id}/thumbnail for a frozen still
- Idle tiles that never recorded show FauxFrame with IDLE badge as before
- Per-tile elapsed timer ticks every second using feed.started_at
- capturePort badge (Port N / SDI N) visible on each tile
- Monitors poll interval tightened from 5s -> 3s for faster live_asset_id pickup

🤖 Generated with Claude Code
2026-06-02 11:15:21 +00:00
Claude
6895fbc5af feat(ingest): show capture port on recorder cards + growing indicator on schedule blocks
- Recorder cards now display a Port chip showing the bridge port (deltacast)
  or SDI device index (blackmagic) so operators can see at a glance which
  physical input each recorder is bound to.

- Schedule blocks render with a green accent + flame glyph when the backing
  recorder has growing_enabled=true, so the EPG view distinguishes
  edit-while-record slots from regular close-then-publish recordings.

🤖 Generated with Claude Code
2026-06-02 04:09:17 +00:00
Zac
46dc17ffb1 fix(web-ui): show live HLS preview for recording assets in library
Live/in-progress assets had no thumbnail_s3_key, so AssetThumb fell
through to FauxFrame (black box) and then an absolute red border div
was drawn on top, producing the 'black box with red outline' symptom.

Fix: when asset.status === 'live', render a new LiveThumb component
instead of FauxFrame + border overlay. LiveThumb attaches hls.js (or
native HLS on Safari) to /live/<assetId>/index.m3u8, shows a muted
live video feed, and displays a 'Connecting…' placeholder with a
record icon + live-colour border while the manifest loads. Falls back
to a 'Recording…' placeholder if hls.js is unavailable or playback
fails after retries.

The red border overlay is removed from the non-live path; the LIVE
badge rendered by AssetCard's thumb-status div still appears on top
of the live video.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:35:27 +00:00
Zac
9ec2997f53 fix(recorder-card): show live framerate and ticking elapsed from capture signal
- _normRecorder: add framerate field (recording_framerate || 'native');
  remove stale static elapsed calc (was computing at poll time, never ticked)
- RecorderRow: replace frozen useMemo elapsed with a live 1s setInterval
  that anchors to liveStatus.duration (authoritative from capture container)
  and falls back to wall-clock diff from recorder.started_at so the counter
  starts immediately on record and never freezes between 3s status polls
- displayFramerate: shows currentFps (2dp + 'fps') when recorder is live and
  currentFps > 0; falls back to configured recording_framerate or 'native'
- Framerate stat block: always visible (was conditional on currentFps != null);
  replaced the separate FPS-only block with a unified Framerate stat
- Also fixes latent padStart(2, '00') typo on minutes field in old elapsed calc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:34:34 +00:00
Zac
bf7189218a fix(web-ui): remove Capture nav item, add Playout testing warning
Remove 'Capture' from the Operations nav section in shell.jsx — users
configure recorders via the Recorders page; the Capture route/component
is left intact for any remaining references.

Also remove 'capture' from the ingest open-group list (it was listed
as an ingest child despite living in Operations, now moot).

Add a prominent amber warning banner at the top of the Playout page
body (screens-playout.jsx) to make clear the feature is in testing and
not ready for production use.

No cherry-pick from fix/library-and-signal-indicator — all commits on
that branch are already present on main.
2026-06-02 00:17:04 +00:00
75f265534e fix(web-ui): default recorder bitrate 60→25 Mbps, HEVC preset 60→25 2026-06-01 18:41:36 -04:00
63654ea0ed feat(deltacast): persist selected channel as source_config.port
The Deltacast picker's selected index is the capture channel on the single
board. Write it into source_config.port (in addition to device_index) so the
capture sidecar maps "pick channel N" to the bridge's --port N. device_index is
retained for backward-compatible display/fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 15:00:48 -04:00
b65ce5b0b7 fix(web-ui): recorder modal body scrolls (min-height:0) so codec/destination aren't clipped at zoom 2026-06-01 13:49:10 -04:00
bda33fedca fix(web-ui): sync route state with URL hash on mount + hashchange
Added useEffect to parse location.hash and update route state.
Fixes deep links like /#/library not rendering correct screen.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-01 11:10:19 +00:00
Zac Gaetano
66ff7065ec Constrain playout preview to 960px for 1080p screens
Cap monitor column at 960px width so full GUI fits 1920x1080 without scroll.
Preview now ~960×540px (16:9), leaves room for 300px rail + margins.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-01 11:09:54 +00:00
Zac
9bcbac558c Add multi-select to library page
- Selection mode toggle in toolbar
- Checkboxes on cards (grid) and rows (list)
- Bulk actions: move to bin, delete
- Select all / clear selection controls

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-01 03:01:19 +00:00
35165e28a8 fix(gui): modal reflects MXF XDCAM growing format (was stale MPEG-TS text)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:14:52 -04:00
32a2d0329e fix(growing+gui): growing file = MXF XDCAM HD422 (Premiere-growable) + GUI fixes
Growing root cause (4th attempt): Premiere doesn't import H.264-in-.ts
("unsupported compression type"); its growing-file support is MXF OP1a.
Prior MXF/DNxHR failed because DNxHR is VBR and never flushes the incremental
index — XDCAM HD422 (mpeg2video, CBR) DOES write index segments into body
partitions mid-record (proven live via SIGKILL: 5 index segments, readable,
no footer). Growing master is now MXF OP1a / XDCAM HD422 4:2:2 CBR + PCM s16le,
operator bitrate as CBR (default 50M). live-path returns .mxf to match.

GUI: bitrate input is now always editable in growing mode (was hidden for
ProRes-selected codecs); codec menu shown disabled-with-explanation under
growing (it had only looked "missing" due to a stale served bundle).

Requires Premiere prefs: Media > "Automatically refresh growing files" ON,
and disable the two XMP-write-on-import options.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:13:01 -04:00
b92a5bc7f7 fix(web-ui): playout fmtDuration no longer clobbers global asset duration formatter
screens-playout.jsx declared a top-level function fmtDuration(secs) that, in
the shared global script scope, overwrote data.jsx's fmtDuration(ms). After
the playout redesign loaded, normalizeAsset(duration_ms) hit the seconds-based
version, rendering every asset duration x1000 (15000ms shown as 4:10:00).
Rename the playout-local helpers to playoutFmtDur/playoutFmtTC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:02:46 -04:00
7b878d48c9 fix(capture): growing TS is 8-bit 4:2:0 (Premiere-importable) + honor bitrate
Growing master was H.264 High 4:2:2 Intra (high422/yuv422p) — ffprobe/VLC
open it but Adobe Premiere's H.264 importer only accepts 8-bit 4:2:0, so it
refused ("won't import"). Switch growing video to -profile:v high
-pix_fmt yuv420p (still -g 1 all-intra). Also the growing branch ignored the
operator's bitrate; now applies -b:v/-maxrate/-bufsize. Modal notes that
growing mode fixes codec/container (bitrate still applies).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:56:58 -04:00
Zac
ef329399f1 feat: v2.2.3 plugin + duration fix
- Add v2.2.3 to downloads (streaming write fix for large imports)
- Fix duration bug: worker now overwrites with ffprobe result instead of preserving capture wall-clock estimate

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-01 01:10:55 +00:00
984a73e8ec feat(playout): redesigned MCR screen + SCTE-35 end-to-end
Drop in the redesigned timeline-centric Playout (PGM monitor, transport,
SCTE-35 card, as-run drawer) from the on-node redesign, fully wired to the
real playout API (channels/transport/HLS preview w/ error-recovery/as-run);
no mock data. In-page ConfirmModal for destructive actions.

SCTE-35: new playout_scte_breaks table (migration 033), endpoints to
schedule/trigger/list/cancel breaks (POST/GET/DELETE /channels/:id/scte[/trigger]),
scheduler due-break sweep, engine triggerScte + auto-return + as-run 'scte'
rows + on-air SCTE-BREAK state and timeline AD markers. In-stream SCTE-35
cue injection is a documented stub (CasparCG FFMPEG consumer exposes no
scte35 muxer) — scheduling/triggering/countdown/as-run are functional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:58:02 -04:00
08499b93b2 feat(gpu+capture): nvenc HLS preview, source-backend abstraction, GPU affinity+telemetry
#164 HLS preview uses h264_nvenc (forced-IDR, GOP=segment) when the sidecar
has the GPU, else keeps libx264 fallback.
#168 source-backend abstraction in capture-manager (blackmagic implemented as
a behavior-preserving refactor; deltacast/aja stubbed pending hardware).
#167 per-recorder gpu_uuid (migration 032) plumbed mam-api->agent->
NVIDIA_VISIBLE_DEVICES (defaults to 'all').
#166 node-agent reports encoder util + NVENC session count per GPU; Cluster
screen renders per-GPU GPU/ENC util, VRAM, sessions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:38:56 -04:00
ca1eec0600 fix/feat: recorder finalize-grace + codec validation, cluster mem/version, library download
#162 local-spawn stop now uses /stop?t=180 + waits for asset to leave 'live'
before removing the container (no more SIGKILL-corrupted masters / stuck-live).
#163 validateRecorderConfig guard (PCM!=MP4, HEVC!=MXF, NVENC needs GPU) on
create+PATCH; codec presets in new-recorder modal.
#159 container list reads Docker /stats memory (N/A when null) + UI render.
#160 primary node self-populates version + uptime on the Cluster screen.
#145 asset-detail Download original gated by dismissable size warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:34:36 -04:00
1d642bd437 feat(web-ui): in-page delete confirm modal + WDL home footer
Replace 17 native window.confirm() destructive prompts with an in-page
ConfirmModal/useConfirm (added to visuals.jsx) across jobs/asset/editor/
ingest/projects/admin/playout/library. Add "Created by Wild Dragon LLC"
footer to the home launcher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:31:07 -04:00
fffff1c016 feat(cluster): install capture-card drivers/SDKs from the admin screen
Per-node "Capture Drivers / SDKs" panel installs Blackmagic / AJA / Deltacast
/ NDI drivers without SSH. node-agent gains NODE_TOKEN-gated /driver/install
+ /driver/status (spawns a one-shot privileged ubuntu container that bind-
mounts host kernel paths + the repo and runs deploy/install-driver.sh);
mam-api adds admin-gated /cluster/:id/install-driver + /driver-status.
Driver files live in-repo under sdk/<vendor>/ (private repo); binaries are
admin-supplied per each sdk/<vendor>/README.md. Vendor allowlist throughout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:14:59 -04:00
f2d5f5aa16 feat(cluster): auto-assign node profiles from detected hardware
onboard-node.sh auto-detects GPU (nvidia-smi/lspci) and SDI capture cards
(blackmagic/deltacast) and computes PROFILES (worker [+gpu] [+capture])
automatically; explicit NODE_ROLE/PROFILES still override. Add Node wizard
drops the role picker — node self-configures from hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:02:29 -04:00
d908c0c056 fix(playout+capture+monitors): preview recovery, SMB UNC, playout monitors
Playout preview: add hls.js ERROR handler (recover media/network errors,
resume on stall) + live-edge tuning — first transient error no longer halts
<video> to black. Purge stale HLS segments before re-mux (re)start so a
prior/duplicate sidecar session can't corrupt the live playlist.

Growing files: normalize growing_smb_mount (smb://, \host\share, host/share)
to CIFS UNC //host/share in capture-manager — mount no longer fails and
falls back to S3.

Monitors: surface playout channels as monitor tiles (live HLS on-air,
idle placeholder otherwise) in a labeled Playout group.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:56:45 -04:00
43011bd794 Merge feat/playout-mcr into main
Playout/MCR, as-run log, redesigned dashboard, capture CIFS/growing-files,
SDI settings, cluster Add Node wizard, homepage refresh.

# Conflicts:
#	services/mam-api/src/routes/cluster.js
#	services/mam-api/src/routes/playout.js
#	services/mam-api/src/scheduler.js
#	services/playout/Dockerfile
#	services/playout/entrypoint.sh
#	services/web-ui/public/screens-home.jsx
2026-05-31 17:46:12 -04:00
19f0abeabe feat(cluster+home): Add Node wizard, homepage tagline/logo/settings tweaks
Cluster: AddNodeModal on Admin->Cluster mints a node token via /auth/tokens
and emits a ready-to-paste curl|bash onboarding command. New admin-only
GET /cluster/onboard-info returns apiUrl/scriptUrl/branch. Role->PROFILES
mapping (worker/capture/gpu); gate worker-l4 behind compose profile [gpu].

Home: restore "Let's Create" kicker + one-line "Media Asset Management &
Production Platform" tagline; animated accent pulse behind the dragon logo
(reduced-motion safe); move Settings tile to a centered bottom row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:37:37 -04:00
f21bc490e8 feat(web-ui): redesigned Dashboard + playout as-run log
Dashboard (screens-home.jsx): rebuild to new design, fully live-wired.
Dropped fabricated figures per "real data" rule (object-store %, uptime,
storage breakdown); repurposed ingest cell to real Assets-24h count.
Fixed undefined refs and double-rendered Resources section.

Playout: as-run writer in scheduler.js writeAsRun() off the health-tick
/status poll; AsRunPanel UI + missing CSS in styles-playout.css.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:15:32 -04:00
d3ad2397fb fix(playout): serve preview m3u8 via /api to bypass the proxy's static cache
Root cause of the persistent black preview, fully isolated: ZAMPP1's nginx
serves the live .m3u8 fresh on every request (no-store works there), but
the PUBLIC reverse proxy (159.112.211.103 -> ZAMPP1) caches the static
.m3u8 by path with a multi-second TTL, ignoring both the origin's no-store
and query params. hls.js reloads the playlist ~every second, always landing
inside that TTL, so it sees the live playlist as never advancing
("live playlist MISSED" forever), never establishes the timeline, and never
loads a fragment -> readyState 0 (black). Proven: rapid reads via ZAMPP1
localhost advance (404->405); the same rapid reads via the public URL are
stuck; query-busting doesn't help (proxy caches by path).

Fix: serve the playlist through GET /api/v1/playout/channels/:id/hls/index.m3u8
instead of the static /media/live path. /api/ is not proxy-cached (the live
status poll already updates fine through it), so hls.js always gets the fresh
live edge. Segment (.ts) lines are rewritten to absolute /media/live/<id>/
URLs so they still load from the static path (immutable; caching them is
correct). ProgramMonitor points hls.js at the /api playlist and sends the
session cookie (xhrSetup withCredentials) since /api is auth-gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:57:41 -04:00
24d10fda5d fix(playout/web-ui): no-store on live HLS m3u8 so hls.js sees the live edge
Definitive root cause of the black preview, proven in-browser: the live
.m3u8 was served Cache-Control: no-cache, so the browser cached the
playlist and served a STALE copy to hls.js's reloads (cache:'default'
stuck at one MEDIA-SEQUENCE while cache:'reload' advanced). hls.js saw the
live playlist as never advancing -> "live playlist MISSED" forever ->
never established the timeline -> never loaded a fragment -> readyState 0
(black), even though the stream itself is clean and advancing server-side.

Fix: serve live HLS (/live and /media/live) with
"no-store, no-cache, must-revalidate" + Pragma no-cache so the browser
never caches the playlist and every reload fetches the fresh live edge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:45:28 -04:00
5968d4f681 feat(settings/growing): storage warning, SMB auth + CIFS mount, per-recorder growing
Implements docs/superpowers/specs/2026-05-31-storage-settings-growing-smb-design.md.

1. Storage warning banner at the top of Settings → Storage (set-once /
   path-change-corrupts-data warning).

2. Growing-files SMB credentials + system CIFS mount (Approach A):
   - settings.js: new global keys growing_smb_mount / growing_smb_username /
     growing_smb_vers; growing_smb_password is write-only (GET returns only
     growing_smb_password_exists; growing_smb_password_clear:true removes it).
   - GrowingSettingsCard: SMB mount/username/password (masked, "saved" state) +
     CIFS version fields.
   - capture Dockerfile: add cifs-utils + util-linux.
   - capture-manager: on growing start, mount //host/share at /growing using a
     root-only credentials file (creds never on the command line); unmount on
     stop; mount failure falls back to S3 streaming so a recording is never lost.
   - recorders.js: pass GROWING_SMB_* env; don't host-bind /growing when a CIFS
     mount is configured (an empty mountpoint is required).

3. Per-recorder growing mode (global toggle removed):
   - Removed the global "capture writes to local SMB share first" checkbox; the
     growing card is now SMB-infrastructure-only.
   - recorders.js reads the per-recorder recorders.growing_enabled column
     (already present from migration 014) instead of the global setting;
     RECORDER_FIELDS += growing_enabled.
   - New-recorder modal: "Growing-files mode" toggle.
   - storage.js overview: "enabled" now means the SMB landing zone is configured
     (mount source set), surfaced as smb_mount; health strip labels updated.

No DB migration required (recorders.growing_enabled exists; new settings are
key/value rows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 14:50:36 -04:00
739d08d4b5 fix(web-ui): restore home screen icon fixes on feat/playout-mcr
playout tile: monitor -> signal, dashboard tile: home -> layout.
All feature-branch content (Dragon-ISO, tagline, etc.) preserved.
2026-05-31 13:56:19 -04:00
1db0e81efb fix(web-ui): restore nav icon fixes on feat/playout-mcr
youtube: import, schedule: clock, playout: signal.
Keeps getting wiped by other agents writing full file replacements.
2026-05-31 13:54:16 -04:00
40e987b4a2 fix(web-ui): restore icon audit fixes on feat/playout-mcr
jobs: bulleted list (not hamburger), import: added, grid: rx=1,
hdd: cylinder, proxy: sliders. Keeps getting wiped by other agents.
2026-05-31 13:52:37 -04:00
d778aa4cdb fix(playout): HLS preview path + live elapsed counter
- nginx.conf: add /media/live/ location serving from the media volume
  mount. CasparCG sidecar writes HLS preview to /media/live/<id>/ but
  nginx only had /live/ (capture volume). Without this, preview
  requests returned the SPA shell instead of the .m3u8 playlist.
- ProgramMonitor: add live elapsed counter (MM:SS, ticks every 500ms)
  driven by engine.currentItemStartedAt. Shows alongside clip index.
  Adds a ⚠ pip when lastError is set (e.g. NDI SDK missing) without
  blocking operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 13:16:57 -04:00
e51cf1aa9c feat(jobs): surface playout-stage queue in Jobs screen
- jobs.js: add playout-stage BullMQ queue to QUEUES; asset_id from
  job data is already resolved to a name by attachAssetNames
- screens-jobs.jsx: map type 'playout-stage' -> kind 'Stage' with
  monitor icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 12:06:08 -04:00
f7cf56ae0d fix(playout): silent-audio staging crash, home tiles, channel delete
- playout-stage: skip loudnorm pass 2 when measured_I=-inf (silent or
  no-audio clip); fall back to plain AAC transcode so staging completes
  instead of erroring out
- screens-home: add Playout tile; replace Premiere panel tile with
  Downloads tile opening a combined modal (Premiere panel releases +
  Dragon-ISO link to forge.wilddragon.net/WildDragonLLC/dragon-iso)
- screens-playout: add Delete channel button (visible only when stopped);
  removes channel from list and selects next on confirm

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 12:03:20 -04:00
12115a053a feat(playout): fix 409 drag bug, add HLS preview, advanced playlist
- Fix event bubbling: e.stopPropagation() in onItemDrop prevents
  duplicate POST when dropping on an existing playlist item
- Wrap all drop handlers in try/catch with inline error display
- ProgramMonitor: replace text placeholder with hls.js video player
  loading /media/live/<channel_id>/index.m3u8; falls back to native
  HLS on Safari; destroys Hls instance on channel stop/unmount
- Playlist: per-item duration (MM:SS), staging progress bar with
  animated stripe while staging, now-playing highlight + ▶ indicator
  driven by engine.currentIndex from 4s status poll
- Playlist footer: clip count + total duration sum
- Transport: Play button disabled + shows ' N staging' until all
  items are media_status=ready, eliminating the staging-not-ready 409

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 11:45:25 -04:00
43656a5e88 shell: youtube nav icon: download → import
download is used by the Downloads section tile on the home screen.
YouTube ingest gets the new import icon (arrow entering box) instead.
2026-05-31 10:52:10 -04:00
68461af990 icons: add import icon (arrow entering box)
Distinct from download (vertical arrow+line). Used for YouTube ingest
to avoid sharing a glyph with the Downloads section tile.
2026-05-31 10:50:43 -04:00
8bc460025d screens-home: fix launcher tile icons
- Dashboard tile: home → layout (matches sidebar nav icon)
- Playout tile: monitor → signal (matches sidebar nav fix)
2026-05-31 00:19:34 -04:00
42064acefa shell: fix nav icon conflicts
- schedule: jobs → clock (was sharing hamburger icon with Jobs)
- playout: monitor → signal (was sharing TV icon with Monitors)
2026-05-30 13:30:42 -04:00
2e2b091653 icons: fix 4 icon issues found in audit
- jobs: replace hamburger (nav menu) with bulleted list (task queue)
- grid: add rx="1" to match library icon (consistency)
- hdd: replace circle+dot (vinyl) with cylinder (storage)
- proxy: replace upload-arrow with sliders (transcode/transform)
2026-05-30 13:29:18 -04:00
Zac
c502d4a16f feat(web-ui): update home tagline + add "Let's create" motto
Tagline "Self-hosted broadcast media-asset management" ->
"Media Asset Management & Production Platform"; add italic accent motto
"Let's create" below it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 16:04:28 +00:00
Zac
9d098e9778 feat(auth-ui): interactive permissions matrix, admin 2FA reset, Downloads button
Backend (routes/users.js):
- GET / now returns totp_enabled so the UI can show 2FA status
- GET /:id/access — admin-only effective per-project access (MAX over direct +
  group grants), labels via=direct|group:<name>; admins report all/edit
- POST /:id/totp/disable — admin clears a locked-out user's 2FA without their
  password (self-service disable still requires it); dev user blocked
- role validated against {admin,editor,viewer} on create + PATCH (was unchecked)

Frontend:
- Users>Policies tab: static prose replaced with interactive per-user matrix —
  inline role select, 2FA badge, Reset-2FA action, lazy per-user access expander
- Home "Premiere panel" tile -> "Downloads"; modal renamed, adds Teams ISO row
  (disabled "coming soon" until the .exe is supplied); UXP .ccx link unchanged
- data.jsx: window.TEAMS_ISO placeholder ({available:false})

Not runtime-tested in browser yet. Teams ISO .exe still pending from user.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:59:27 +00:00
Zac
f837e57969 feat(web-ui): add Playout tile to home screen
Fetches /playout/channels separately and degrades silently when the
endpoint or schema is absent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 14:59:59 +00:00
Zac
793011b78b feat(web-ui): MCR page — channels, playlist, transport, preview
screens-playout.jsx + styles-playout.css: program monitor (HLS preview from
the sidecar), media bin, drag-drop playlist editor, transport controls. Plain
HTML5 drag-drop, no extra library. Talks to /api/v1/playout via
ZAMPP_API.fetch.

Wired into the shell: "Playout" under Operations, breadcrumb mapping, route
case in app.jsx, stylesheet + dist/screens-playout.js script in index.html.
Format dropdown defaults to 1080p5994 (matches the new channel default).
2026-05-30 14:02:25 +00:00