Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
6.2 KiB
6.2 KiB
CCLI SongSelect Import — Learnings
[2026-05-10] Session Start
Architecture Decisions
- Manual paste + bookmarklet approach (NO server-side scraping — Cloudflare/ToS blocker)
- CcliPasteParser is closure-injectable (mirrors ChurchToolsService pattern for testability)
- All songs upserted via CcliImportService mirroring ProImportService::import() shape
- Translation stored inline on SongSlide.text_content_translated (no separate model)
- default_translation_language = APP-GLOBAL Setting (not per-user)
Key Codebase Facts
- Song.ccli_id is UNIQUE indexed nullable — primary CCLI match key
- SongSlide: text_content (original), text_content_translated (translation)
- Labels are GLOBAL (shared across all songs) — labels table with name + color
- ProImportService::import() is the template for upsert (not upsertSong — that method doesn't exist)
- SettingsController::AGENDA_KEYS constant whitelist for Settings KV
- TranslationService::importFromText distributes lines preserving local slide line counts
- ArrangementDialog.vue lines 488-532 = searchable song select (where CCLI buttons go)
CCLI SongSelect "View Lyrics" Page Format
- Title on first non-empty line
- Section label as standalone line (e.g., "Verse 1", "Chorus")
- Lyrics lines under each section
- Footer: copyright (©), CCLI number (e.g., "CCLI # 1234567"), author
Section Label Regex (English + German + variants)
/^(Verse|Chorus|Bridge|Pre-Chorus|Tag|Ending|Intro|Interlude|Outro|Misc|Strophe|Refrain|Brücke|Vorrefrain|Schluss|Zwischenspiel)\s*(\d+[a-z]?)?(\s*\((?:Repeat|Wdh\.?)\))?(\s*[xX]\s*\d+)?$/i
Language Mapping (EN ↔ DE)
- Verse ↔ Strophe
- Chorus ↔ Refrain
- Bridge ↔ Brücke
- Pre-Chorus ↔ Vorrefrain
- Ending ↔ Schluss
- Interlude ↔ Zwischenspiel
Test Fixture Format
Fixtures are synthetic CCLI-format text files. Format:
Test Song Title
Test Artist
Verse 1
Line 1 of verse
Line 2 of verse
Chorus
Chorus line 1
Chorus line 2
© 2024 Test Publishing
CCLI # 9999001
Fixture Corpus Notes
- Keep fixture titles/artists anonymized and numeric (
Test Song N,Test Artist N) - Include both English and German section labels in the corpus so parser regex coverage stays broad
- Add edge cases for missing footer pieces, whitespace, repeat markers, and suffix labels (
2a,x2,(Repeat))
2026-05-10 CCLI Label Utility Notes
CcliLabelsworks best with a fixed kind list in regexes; no locale config needed for EN/DE normalization.normalizeLabelName()should map only known German kinds and preserve any numeric suffix.parseLabel()can stay lightweight by returningnullfor non-labels and a small array for matched labels.
2026-05-10 Song CCLI Metadata Migration
- Song CCLI metadata belongs on
songsas nullable fields:imported_from_ccli_at(timestamp) +ccli_source_url(string 500). - Factory state helpers can stay tiny;
fromCcli()just seeds timestamp + SongSelect URL. - Inference/LSP can lag after edits; a tiny no-op signature change (
fn (): array => [...]) forced the factory diagnostics to refresh cleanly.
2026-05-10 Settings Language Seed
SettingsController::AGENDA_KEYSdrives both the index props and the allowedkeyvalues for PATCH updates.default_translation_languageshould be validated as a whitelist value (DE|EN|FR|ES|NL|IT) only when that setting is being updated.CcliSettingsSeedermust useSetting::firstOrCreate()so reseeding does not overwrite a user-changed language.
2026-05-10 CcliPasteParser Scaffold
- Mirror
ChurchToolsServicewith nullableClosureconstructor injections and default= nullvalues. - This codebase uses
App\Services\DTO\...namespaces/directories for DTOs, so keep the uppercaseDTOpath aligned with existing services. - Scaffold tests can verify Laravel container resolution without adding any service provider binding.
2026-05-10 CcliPasteParser Implementation
- Parser trims pasted lines, treats blank lines as separators, extracts first two header lines as title/author, and excludes CCLI metadata from lyric sections.
- EN/DE side-by-side imports merge only adjacent labels with different raw kinds but the same
CcliLabels::normalizeLabelName()canonical kind/number, preserving German lyrics inlinesTranslated. - DDEV/Linux path is
tests/fixtures/ccli(lowercase); macOS acceptedtests/Fixtures/ccli, but tests must use lowercase for container portability.
2026-05-10 CcliImportService Implementation
CcliImportServicemirrorsProImportServiceby wrapping song metadata, global label resolution, slide replacement, default arrangement upsert, arrangement-label recreation, andApiRequestLogsuccess entry in oneDB::transaction().- Active duplicate CCLI IDs are blocked with
DuplicateCcliSongException; trashed matches are restored and updated in-place viaSong::withTrashed()->where('ccli_id', ...). - CCLI label names should be canonicalized with
CcliLabels::normalizeLabelName($kind.' '.$number)beforeLabel::firstOrCreate(), keeping labels global/shared. - Import tests can verify rollback deterministically with a temporary SQLite trigger that aborts
song_slidesinsert; this proves song + log rows are not persisted after mid-transaction failure.
2026-05-10 CCLI Parser Review Fixes
- CCLI SongSelect metadata can appear as
CCLI Song #,CCLI-Nr.orCCLI-Liednummer; extraction must ignoreCCLI License/Lizenznumbers. - Parsed section
kindis canonicalized viaCcliLabels::normalizeLabelName(), while the original pasted label remains available inlabel; translation pairing still compares raw label kinds internally.
2026-05-10 CCLI Translation Pairing
CcliTranslationPairingServicereturns a review-only mapping and never writesSongSlide.text_content_translated; callers remain responsible for persistence.- Pairing canonicalizes both local arrangement labels and CCLI sections with
CcliLabels::normalizeLabelName()+ lowercase, soStrophe 1↔Verse 1andRefrain↔Choruswork across languages. - Distribution mirrors
TranslationService::importTranslation()by filling local slide slots in arrangement order using each local slide's original line count; overflow CCLI lines are kept on the final local slide for that section.