Skip to content

feat(setbuilder): Engine DJ + Lexicon exports via Rekordbox XML (#401)#453

Merged
thewrz merged 6 commits into
mainfrom
feat/issue-401
Jun 18, 2026
Merged

feat(setbuilder): Engine DJ + Lexicon exports via Rekordbox XML (#401)#453
thewrz merged 6 commits into
mainfrom
feat/issue-401

Conversation

@thewrz

@thewrz thewrz commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Why

Issue #401 asks for Engine DJ and Lexicon as export targets. Research
(captured in the design doc) established that neither platform has a
proprietary import format
— both ingest the Rekordbox DJ_PLAYLISTS
XML WrzDJ already generates. So the honest, low-risk shipping path is
labeled presets of the existing renderer, not two new serializers. The
one real gap surfaced along the way: ISRC had no slot in DJ_PLAYLISTS
and was silently dropped
on every export.

What

  • ISRC fidelity fix (benefits rekordbox/Engine DJ/Lexicon alike): the
    Rekordbox XML renderer now emits track.isrc via the TRACK Comments
    attribute as ISRC:<isrc>, run through the existing control-char
    sanitizer. Absent ISRC omits Comments.
  • Engine DJ + Lexicon export targets: widened the export schema
    Literals with distinct enginedj/lexicon keys (not aliases — so the
    UI lists them separately and future per-platform tweaks don't churn the
    contract) and mapped both to render_rekordbox_xml
    (application/xml, .xml).
  • ExportModal: Engine DJ flips from "coming soon" to shipped, Lexicon
    is added; both download the Rekordbox XML through one shared
    XML_PLATFORMS block, with an in-modal import-then-relink note.
  • Regenerated server/openapi.json and dashboard/lib/api-types.generated.ts.

Out of scope (documented as non-viable in the design): Engine Library
SQLite DB writes (Denon-discouraged) and the Lexicon Local API
(localhost / unauth / read-mostly).

Testing

  • Backend unit/integration tests pass (pytest — 2891 passed, coverage 88% ≥ 85% gate)
  • Renderer tests: ISRC → Comments, omitted when absent, control chars sanitized
  • API tests: enginedj/lexicon route to the XML renderer (200, application/xml, .xml); preflight uses the file branch; export does not mutate set status
  • Frontend tests pass (vitest — 1264 passed); ExportModal picker shows 8 rows / 3 "coming soon", Engine DJ + Lexicon download .xml and show the relink note
  • ruff check / ruff format / bandit clean; npm run lint / tsc --noEmit clean
  • alembic upgrade head && alembic check — no new migration, no drift
  • Manual QA (out of CI): import the exported .xml into current Engine DJ (Database tab → set Rekordbox XML path → Import) and Lexicon (import Rekordbox XML), confirm the playlist + metadata land and tracks relink to local audio

🤖 Co-authored by Claude Opus 4.8. Closes #401.

Summary by CodeRabbit

  • New Features
    • Enabled Engine DJ and Lexicon as direct export options in the export modal.
    • Exporting for these options now downloads XML using the existing Rekordbox XML workflow.
    • Added in-app guidance describing the import → track relink flow for these XML exports.
  • Bug Fixes
    • Improved ISRC preservation in exported XML (including safe handling of special/control characters).
  • Documentation
    • Published the design specification and implementation plan for the new export targets.

thewrz and others added 5 commits June 17, 2026 17:48
Capture the approved design for exposing Engine DJ and Lexicon as
export targets. Research established both platforms import the existing
Rekordbox DJ_PLAYLISTS XML, so the work is labeled presets of the
existing renderer plus an ISRC fidelity fix — not new serializers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ordered TDD plan: ISRC fidelity fix, schema Literal widening, renderer
registration, OpenAPI + generated-types regen, and the ExportModal
Engine DJ + Lexicon options.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DJ_PLAYLISTS has no native ISRC attribute, so the identifier was
silently dropped on every rekordbox/Engine DJ/Lexicon export. Carry it
in the TRACK Comments attribute as "ISRC:<isrc>", sanitized through the
existing control-char cleaner. Absent ISRC omits Comments entirely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neither platform has a proprietary import format — both ingest the
Rekordbox DJ_PLAYLISTS XML WrzDJ already generates. Widen the export
schema Literals with distinct "enginedj"/"lexicon" keys and map both to
render_rekordbox_xml (application/xml, .xml). Distinct keys (not aliases)
keep them listed separately in the UI and leave room for future
per-platform tweaks without churning the contract. Regenerates
openapi.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flip Engine DJ from "coming soon" to shipped and add a Lexicon option.
Both download the existing Rekordbox DJ_PLAYLISTS XML (neither platform
has a proprietary import format), so they share one download path with
rekordbox via XML_PLATFORMS, plus an in-modal import-then-relink note.
Regenerates api-types.generated.ts so the widened export Literals reach
the frontend types.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 58631263-138d-49b2-af01-0dc48697ed4d

📥 Commits

Reviewing files that changed from the base of the PR and between ea45d15 and f8d77e3.

📒 Files selected for processing (2)
  • dashboard/app/(dj)/setbuilder/components/__tests__/ExportModal.test.tsx
  • server/tests/test_setbuilder_export_api.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • dashboard/app/(dj)/setbuilder/components/tests/ExportModal.test.tsx
  • server/tests/test_setbuilder_export_api.py

📝 Walkthrough

Walkthrough

Adds enginedj and lexicon as first-class export targets by routing both through the existing Rekordbox XML renderer. Backend schemas, the _FILE_RENDERERS registry, and OpenAPI enums are widened; the XML renderer gains ISRC emission in the track Comments attribute. Generated TypeScript types are updated. The ExportModal enables both new options with relink guidance and new tests cover all layers.

Changes

Engine DJ + Lexicon Export Targets

Layer / File(s) Summary
Backend schema literals, ISRC renderer, and API routing
server/app/schemas/setbuilder.py, server/app/services/setbuilder/export_files.py, server/app/api/setbuilder.py
ExportTarget and ExportFileFormat Literal unions gain enginedj and lexicon; render_rekordbox_xml conditionally writes ISRC:<cleaned> into the XML Comments attribute; _FILE_RENDERERS maps both new keys to the Rekordbox XML renderer with application/xml and .xml.
OpenAPI schema and generated TS types
server/openapi.json, dashboard/lib/api-types.generated.ts
ExportFileIn.format, ExportPreflightIn.target, and ExportPreflightOut.target enums in server/openapi.json are extended to include enginedj and lexicon; the regenerated TS unions in api-types.generated.ts reflect the same additions.
ExportModal platform wiring and confirmation UI
dashboard/app/(dj)/setbuilder/components/ExportModal.tsx
Platform catalogue marks enginedj and lexicon as available and adds them to XML_PLATFORMS; preflight target map and file-extension map are extended; confirmation-stage XML download block renders for all XML_PLATFORMS and conditionally shows relink guidance for non-Rekordbox XML targets.
Backend tests: ISRC, preflight, file export, status immutability
server/tests/test_setbuilder_export_service.py, server/tests/test_setbuilder_export_api.py
Service tests cover ISRC presence, absence, and control-char sanitization in rendered XML; API tests assert enginedj/lexicon preflight returns source: "pool", both formats produce application/xml output matching Rekordbox XML, and an enginedj export leaves set status unchanged at "draft".
Frontend tests: picker and export flow
dashboard/app/(dj)/setbuilder/components/__tests__/ExportModal.test.tsx
Picker assertions updated to expect 8 rows with 3 "Coming soon" badges; Engine DJ and Lexicon verified as enabled. Parameterized it.each tests cover preflight → XML download for both targets and assert the relink/import note renders.
Design spec and implementation plan
docs/superpowers/specs/2026-06-17-issue-401-design.md, docs/superpowers/plans/2026-06-17-issue-401-engine-lexicon-export.md
New design specification and implementation plan document the architecture, ISRC fidelity rationale, file-change checklist, test requirements, and out-of-scope boundaries for this feature.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • wrzonance/WrzDJ#421: This PR extends the Rekordbox XML export flow, render_rekordbox_xml renderer, _FILE_RENDERERS registry, ExportTarget/ExportFileFormat schemas, and ExportModal UI that PR #421 originally established.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding Engine DJ and Lexicon as export targets via Rekordbox XML, and correctly references the associated issue.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #401: Engine DJ and Lexicon exports are functional with Rekordbox XML, schema expanded, API routes registered, frontend modal updated with both platforms shipped, and comprehensive test coverage added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the export target implementation. Documentation files outline the implementation plan and design. Backend, frontend, tests, and API types are all aligned with the issue's export functionality objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-401

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
dashboard/app/(dj)/setbuilder/components/__tests__/ExportModal.test.tsx (1)

444-451: 💤 Low value

Consider extending the relink-note test to cover both targets.

The relink note test only verifies enginedj. Since the UI condition is platformId !== 'rekordbox', both enginedj and lexicon should show the note. For completeness, consider parameterizing this assertion or adding a second case for lexicon.

♻️ Optional: parameterize the relink-note assertion
-  it('shows the import-then-relink note for these targets', async () => {
-    mockApi.exportPreflight.mockResolvedValue(makePreflightClean('enginedj'));
-    render(<ExportModal {...baseProps} />);
-    fireEvent.click(screen.getByText('Engine DJ XML'));
-    await waitFor(() => {
-      expect(screen.getByText(/relink/i)).toBeTruthy();
-    });
-  });
+  it.each([
+    ['Engine DJ XML', 'enginedj'],
+    ['Lexicon', 'lexicon'],
+  ])('%s shows the import-then-relink note', async (label, format) => {
+    mockApi.exportPreflight.mockResolvedValue(
+      makePreflightClean(format as ExportPreflight['target'])
+    );
+    render(<ExportModal {...baseProps} />);
+    fireEvent.click(screen.getByText(label));
+    await waitFor(() => {
+      expect(screen.getByText(/relink/i)).toBeTruthy();
+    });
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/app/`(dj)/setbuilder/components/__tests__/ExportModal.test.tsx
around lines 444 - 451, The test "shows the import-then-relink note for these
targets" only verifies the relink note displays for the 'enginedj' target, but
since the UI condition checks platformId !== 'rekordbox', both 'enginedj' and
'lexicon' should show this note. Extend the test to cover both targets by either
parameterizing the test to run the assertion for each target separately, or
adding a second test case that clicks on the lexicon option and verifies the
relink note appears for that target as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/tests/test_setbuilder_export_api.py`:
- Around line 283-292: The test_enginedj_export_does_not_mutate_status method
posts an export request to the client but does not validate that the request
succeeded before checking the status. Capture the response from the client.post
call and assert that it returns a successful status code (such as 200) before
proceeding with the db.expire_all() and the assertion that verifies the Set
status remains as "draft". This ensures the test is actually validating the
correct behavior and not passing due to a failed request.

---

Nitpick comments:
In `@dashboard/app/`(dj)/setbuilder/components/__tests__/ExportModal.test.tsx:
- Around line 444-451: The test "shows the import-then-relink note for these
targets" only verifies the relink note displays for the 'enginedj' target, but
since the UI condition checks platformId !== 'rekordbox', both 'enginedj' and
'lexicon' should show this note. Extend the test to cover both targets by either
parameterizing the test to run the assertion for each target separately, or
adding a second test case that clicks on the lexicon option and verifies the
relink note appears for that target as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b8ee6e1-b9ed-418c-9237-3ac16c830e14

📥 Commits

Reviewing files that changed from the base of the PR and between fbc46f6 and ea45d15.

📒 Files selected for processing (11)
  • dashboard/app/(dj)/setbuilder/components/ExportModal.tsx
  • dashboard/app/(dj)/setbuilder/components/__tests__/ExportModal.test.tsx
  • dashboard/lib/api-types.generated.ts
  • docs/superpowers/plans/2026-06-17-issue-401-engine-lexicon-export.md
  • docs/superpowers/specs/2026-06-17-issue-401-design.md
  • server/app/api/setbuilder.py
  • server/app/schemas/setbuilder.py
  • server/app/services/setbuilder/export_files.py
  • server/openapi.json
  • server/tests/test_setbuilder_export_api.py
  • server/tests/test_setbuilder_export_service.py

Comment thread server/tests/test_setbuilder_export_api.py
- Assert export POST returns 200 before checking the set status stays
  "draft", so the no-mutation test cannot pass on a failed request.
- Parameterize the import-then-relink note test over both Engine DJ and
  Lexicon, since the note renders for every non-rekordbox XML target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thewrz

thewrz commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

CodeRabbit body nitpick addressed (🧹 ExportModal.test.tsx 444-451 — "extend the relink-note test to cover both targets"): fixed in f8d77e3. The single-target relink-note test is now parameterized via it.each over both Engine DJ XML and Lexicon, matching the platformId !== 'rekordbox' UI condition. tsc, ESLint, and Vitest (15 passing) all green locally.

@thewrz thewrz merged commit 48f97b3 into main Jun 18, 2026
11 checks passed
@thewrz thewrz deleted the feat/issue-401 branch June 18, 2026 01:33
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.

WrzDJSet: Engine DJ XML + Lexicon exports

1 participant