Package the redesigned UXP panel as dragonflight-mam-2.2.2.ccx (plain zip
of manifest + index.html + styles.css + src/*, the standard UXP format that
double-click-installs on Windows and Mac via Creative Cloud).
- Add the .ccx to web-ui/public/downloads (served by nginx at /downloads).
- data.jsx: new v2.2.2 entry as latest (ccx field); old CEP releases kept.
- Editor screen: single "Download Panel (.ccx)" button.
- Settings -> Capture SDKs: .ccx download + updated UXP install copy;
per-release buttons fall back to ZXP/Win for the old CEP entries.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The floating popup anchored to the rail button clipped at the panel edge
(the bottom option was cut off). Replace it with a full-panel screen that
takes over the whole panel: a header with close, and two large option
rows (Conform Timeline -> MAM, Local Export) with icon + description.
Conform still routes to the codec-picker panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI consolidation:
- One Export entry (rail) opens a popup menu: Conform Timeline -> MAM and
Local Export. Retires the standalone Export & Conform / Fetch & Relink
dock buttons and the plain "Push Timeline" flow.
- Remove Import All.
- New "Upload to MAM" dock button.
Upload: reads the highlighted project-panel item(s) via the premierepro
API (best-effort, guarded) and falls back to a native file picker. Pushes
via /upload/simple (small) or chunked init/part/complete (large).
Local Export: batch-trim the timeline's hi-res clips server-side (FFMPEG),
poll trim-status, download each temp-segment-url, relink in Premiere.
Relink keys on source media path (last-wins for multi-use sources).
Conform + Local Export auto-upload any timeline sources not yet in the MAM
before proceeding (renders from the hi-res original, available post-upload).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preset cards overlapped the codec/quality dropdowns because the
2-column .preset-grid used flex-wrap, and UXP miscomputes wrapped-flex
container height (collapses to ~0), so the fields below rendered on top.
Rethink:
- Presets are now a single-column list (title left, spec right); no
flex-wrap, so nothing collapses or overlaps.
- Progressive disclosure: the Codec/Quality/Resolution/Audio dropdowns
are wrapped in #conform-custom and hidden unless the Custom preset is
chosen. Default view is just Target project + 4 format rows.
The preset->select value contract is unchanged; presets still populate
the (now hidden) selects that Start Conform reads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tooltips: the floating .tip-bubble used position:fixed, which UXP's
engine does not render reliably, so tooltips never appeared. Switch to
position:absolute (body never scrolls, so viewport coords still map),
add scroll-offset compensation, and guard against UXP returning 0 for
window.innerWidth/innerHeight.
List view: add a toolbar toggle (grid vs compact list). List view shows
a small 50x28 thumbnail with name/meta and an inline status chip per
row, fitting many more clips on screen. Defaults to list, persists via
localStorage when the host allows it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ROOT CAUSE: UXP renders native <button> chrome that ignores CSS
`background` and does not draw <svg>-only button content. The original
panel "worked" only because its buttons had TEXT (native buttons render
their text label); the redesign stripped text out, leaving empty grey
pills. Non-<button> elements (the <span> status chips, the <label>
search field + magnifier) render custom backgrounds and SVG children
correctly in this exact UXP -- proof divs are the right vehicle.
FIX: convert the 12 rail/dock/menu icon controls from <button> to
<div role="button" tabindex="0">. Divs have no native `disabled`, so
main.js installs a `disabled` accessor that reflects to a [disabled]
attribute; CSS keys disabled styling off [disabled]. Reverted the
non-working ::before cover back to direct backgrounds (divs honor them).
Text buttons (Connect, slide-panel actions, glyph close) stay <button>.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UXP's native button chrome overrides explicit `background` rules on
icon-only <button> elements -- appearance:none and background both lose.
Authored content (::before pseudo-elements) renders above native chrome,
so move all background/hover/active logic there. SVG icons and the
growing-count badge get z-index:1/2 to sit above the cover.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
UXP's SVG renderer does not draw Feather/Lucide-style stroke icons
(fill="none" stroke="currentColor"); they showed as blank/grey shapes
in Premiere. Convert all 14 panel icons to filled single-path
(Material-style) SVGs with explicit width/height attributes, which
UXP's simple-icon renderer handles reliably.
Also replace the transparent rail/icon button backgrounds with an
explicit --bg-base fill: appearance:none alone did not suppress UXP's
native grey button chrome, but an explicit background does (same trick
the working .btn rule relies on).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UXP renders native <button> appearance (grey rounded fill) unless
appearance:none is set. .btn masked it with an explicit background,
but .rail-btn / .iconbtn use a transparent background, so the native
chrome showed through as grey blobs and suppressed the SVG icons.
Add appearance:none to both, matching the input/select reset.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the text-heavy panel layout with a minimal icon-first UI:
a VS Code-style vertical activity rail for view switching plus a
contextual icon dock for clip actions. Every control carries a
[data-tip] label surfaced on hover via a JS-positioned tooltip
bubble (UXP's CSS engine can't be trusted with content: attr()).
All existing main.js element IDs and the JS wiring contract are
preserved; the dropped advanced section was already guarded.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two cooperating bugs left Export Timeline stuck at "Rendering Hi-Res"
forever:
A. worker emitted "Invalid FCP XML: no sequence element" because
Timeline.generateFcpXml produced fcpxml (FCP X schema:
<fcpxml><resources>/<library>/...) while the worker's parseFcpXml
expects xmeml (FCP 7 schema: <xmeml><sequence>...). Two completely
different formats.
Rewrite generateFcpXml to emit xmeml v5 with the structure the
parser walks:
xmeml/sequence/{name,duration,rate{timebase,ntsc},
media/video/{format/samplecharacteristics,
track[@currentExplodedTrackIndex]
/clipitem/{name,duration,rate,in,out,
start,end,file/{name,pathurl}}}}
Clipitem in/out are SOURCE frames (the underlying media in/out);
start/end are TIMELINE frames (the cut position). The worker uses
the rate timebase to parse them.
B. /api/v1/jobs/:id rejected the panel's polls with
"Invalid id — must be a UUID". The handlers below correctly parse
BullMQ-prefixed ids ("conform:42"), but router.param('id',
validateUuid('id')) ran first and 400'd everything that wasn't a
UUID. The panel's pollConform swallows the resulting fetch error
silently and polls forever.
Drop the validator. Comment in the file explains why.
Bumps panel to v2.2.2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contract: clicking Export Timeline does the whole pipeline with no
prompts. Behavior matches what the user actually expected from the
button label:
1. readActiveSequence — pulls the Premiere timeline + clip map
2. resolveExportProject — picks the target MAM project. First run
uses the first project on the server and caches its id in
localStorage (df.uxp.exportProjectId). Subsequent runs reuse
the cache. If the cached project was deleted server-side we
transparently re-pick.
3. Timeline.startConform with sensible defaults:
codec=prores_hq, quality=high, resolution=source, audio=broadcast
This both pushes the sequence + clip rows AND queues a real
conform job (the prior Push-to-MAM button never queued a job,
which is why "no jobs spin up" happened earlier).
4. pollConform every 2s, mapping job progress 20→95% on the
panel progress bar.
5. On completion, toast + Library.refresh() so the rendered hi-res
asset shows up in the grid without needing to click around.
The Conform slide panel stays wired for Advanced → Export & Conform
so power users can still override the codec/preset for one-off jobs.
The Push-only slide panel that this replaces is now orphaned chrome
and will be removed in a later cleanup.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User feedback after v2.1.9: panel still chrome-heavy. The Asset Info
panel duplicates what the card already shows; 8 buttons across 3
full-width rows still claim too much vertical real estate.
Three surgical changes:
1. Drop the Asset Info details panel entirely. Card meta (name +
duration + codec) already carries everything we showed in the
key:value table. Library._showDetails / hideDetails become no-ops
so the existing call sites in main.js + library.js don't need
conditional branches.
2. Shrink .action-row .btn to 20px tall, 10.5px font, 6px horiz
padding, 3px radius. Two rows of compact buttons fit where one
bulky row used to.
3. Collapse Advanced section behind a toggle (▸ / ▾). Default
collapsed so the main 6 buttons stay the primary action surface;
click the row to expand and reveal Export & Conform / Fetch &
Relink All.
Per DESIGN.md "density over whitespace."
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UPIA stacks every install in its own
C:\Program Files\...\UXP\Plugins\External\net.wilddragon.dragonflight.uxp_<version>\
folder without removing prior versions. After 10 deploys today there are
11 of them coexisting, and Premiere's loader can pick the wrong one,
which is why v2.1.8 didn't appear to land.
This change makes the running version visible at a glance:
- main.js reads manifest.json at runtime via require('uxp').storage
.localFileSystem.getPluginFolder() so the displayed version is
whatever Premiere actually loaded — never a hand-edited constant
that could drift.
- index.html adds #panel-version inside the status strip (between
host and ⋯) and #brand-version below the brand tag on connect.
- styles.css: small mono chip in --text-4, low key but readable.
If the chip ever shows the wrong version we know the loader picked
a stale dir; if it shows nothing the manifest read itself failed.
The install script needs to remove old _<version> dirs going forward;
the next commit will add that cleanup step to the deploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three changes, surgical so timeline.js / conform / relink / growing
all keep working:
A. Header → 24px status strip + ⋯ menu
`connected-bar` rule kept as an alias to `.status-strip` so any code
path that still emits the old class falls through cleanly. Markup
replaced with .signal-dot + #connected-host + .btn-ghost ⋯ that
toggles a .menu containing the Disconnect button. Menu auto-closes
on outside click. Reclaims ~12px of permanent vertical chrome and
removes the always-visible Disconnect.
B. Compact action footer
`.action-row .btn` now: 22px tall, 11px font, 0.01em letterspacing.
`.advanced-section .action-row .btn` goes a step smaller (20px /
10.5px). Global `.btn` untouched so #connect-btn stays at full
weight on the connect pane.
D. Token alignment with services/web-ui DESIGN.md
--bg-0 #0B0D11 (was #0e0f12), --accent #5B7CFA (was #4f7cff),
plus the full --text-1..4 / --success / --warning / --danger / --live
palette. Legacy --ok / --warn aliased to --success / --warning so
existing rules keep resolving.
C (per-card meta) was already in v2.1.7 — no change needed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
getStartTime/getEndTime/getInPoint/getOutPoint can return null for
non-clip track items (gaps, transitions) that slip past the
getProjectItem check. Accessing .seconds on null threw a TypeError
that the outer catch swallowed — silently dropping every clip and
leaving clips[] empty, so the export panel never opened.
Also skip clips where all four time values resolve to 0 (filler items).
- _writeBuffer: catch EBUSY (Windows file-lock) and treat as success —
the file is already there from the previous import and Premiere has it
locked; no need to re-write it.
- proxy / hires: stat the destination first; if the file already exists
skip the download entirely and go straight to importIntoProject.
- importIntoProject: importFiles returning false means the file is
already in the Premiere project — not an error, treat as success.
img.src direct assignment never sends Authorization headers, so all
thumbnail requests returned 401 once the global auth gate was enabled.
Now fetches via API.request(), converts response to a blob URL, and
assigns that to img.src. Falls back to the placeholder div on error.