The conform worker's final step INSERTs the rendered output into the
assets table:
INSERT INTO assets (project_id, filename, display_name, …)
VALUES ($1, …)
-- project_id NOT NULL
It reads projectId from job.data, but the /sequences/:id/conform
endpoint never set it. Render finished cleanly, ffmpeg ran, output
uploaded to S3, then the final asset row INSERT failed:
null value in column "project_id" of relation "assets"
Pass seq.project_id from the loaded sequence row. The rendered output
lands as an asset under the same project as its source sequence —
the natural target.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Panel had been sending xmeml with clipitem/name = the local Premiere
file path's basename (e.g. "dragonflight-Interstellar - Docking Scene
1080p IMAX HD.mp4"). The worker's old filename lookup ran
SELECT id, original_s3_key FROM assets WHERE filename = $1
which never matched, because the assets row's filename is the
original MAM ingest name without the "dragonflight-" prefix.
Fix: when job.data has sequenceId (always set by the conform endpoint
at routes/sequences.js:317), pull edits directly from sequence_clips,
which the panel already wrote with authoritative asset_id mappings on
push. We JOIN to assets for original_s3_key + filename and order by
(timeline_in_frames, track) so segment indices stay deterministic.
The XML is still parsed for sequence-level metadata (name, fps) when
provided, but its clipitems are no longer authoritative.
The legacy filename path (EDL input or fcpXml without sequenceId)
stays unchanged for backward compatibility.
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.
UXP's `os` is a stripped subset of Node's — `os.tmpdir()` isn't exposed in
the build PPro 26.0.x ships, so both Import Proxy and Import Hi-Res failed
immediately with "os.tmpdir is not a function".
Replace with a defensive resolver tried in order:
1. os.tmpdir if present (newer UXP builds)
2. require('uxp').storage.localFileSystem.getTemporaryFolder() → .nativePath
(the documented portable approach)
3. process.env.TEMP / TMP / LOCALAPPDATA\\Temp (Windows always sets these)
4. os.homedir() + AppData/Local/Temp
tempPath() is now async; both Import.proxy and Import.hires await it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The v2.0.0 grid stayed empty in Premiere 26 because UXP's CSS engine
doesn't support `grid-template-columns: repeat(auto-fill, minmax(...))`
or `aspect-ratio`. Cards rendered with 0 height and the flex column
collapsed, so the actions row stuck to the top of the pane.
Switch to flex-wrap with fixed-width (140px) cards and explicit 80px
thumb heights — both work in UXP's stripped CSS.
Also fix the /auth/me response shape — it returns the user fields
directly, not wrapped in `{ user: ... }`. Header now shows
"display_name @ host" instead of falling back to bare host.
Add a toast on each library load reporting "Loaded N assets (total M)"
so we can tell empty-grid (zero assets) from CSS-broken-grid (cards
exist but invisible) at a glance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>