File-upload + local faster-whisper synced transcription, segment editor,
SRT export, with speed/silence-removal phases. N150 CPU, small int8 model.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add .playwright-mcp/ and verify-*.png to .gitignore (transient scratch).
Untrack .claude/settings.local.json and scheduled_tasks.lock, which were
already listed in .gitignore but still indexed; settings.local.json stays
on disk as a local-only file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dashboardSummary surfaced the earliest-scheduled packages as "recent"
(it reused the queue's scheduledAt-asc sort); query updatedAt-desc instead.
Build byStatus from an explicit ordered list since Set.of iteration order
is undefined, so the dashboard renders DRAFT/READY/PUBLISHED consistently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove hardcoded DB credentials and YouTube API key fallbacks from
application.yml; resolve them from env vars or an optional, git-ignored
application-local.yml (spring.config.import). Add a tracked
application-local.yml.example template and ignore the real local file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First tests in the project (src/test was absent). Add testRuntimeOnly
junit-platform-launcher (required by Gradle 9 to load the JUnit Platform),
then cover:
- VideoMetrics: duration parse, isShorts boundary, viewsPerHour clamp/
division, viewsPerSubRatio rounding & zero-guards (9 cases, pure)
- DashboardService.summary(): composes each domain service under the
right key (Mockito)
- PublishService.dashboardSummary(): status counts + recent capped at 5
12 tests, all green. No Spring context / DB needed — fast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- publish.html: status column uses shared .badge (badge-muted/warning/
success) instead of its own .st-badge inline-color span
- collection.html / discover.html: drop .filter-sel rules now that the
shared `select` styling covers them (row-sel kept for compact inline)
- style.css: .gap-2 uses var(--space-2) for token consistency
No behavior change; follow-up cleanup from the design-uplift review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The discover @Query used the `(:param is null or col >= :param)` idiom.
For the timestamp (publishedAfter) and numeric (minRatio) params, Postgres
threw "could not determine data type of parameter $1" because the bare
`$1 is null` placeholder is untyped — returning HTTP 500 and breaking the
whole /discover page. (The search() query survives because its nullable
params are only bigint/varchar, which Postgres can null-type.) Wrap the
two problematic params in cast(... as timestamp/big_decimal) in the
is-null check so the type is explicit. ORDER BY ... nulls last / fetch
first were never the problem.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shared-CSS-first strategy (variables.css + style.css) to lift all 12
Thymeleaf pages, then per-page polish + light interactions. Minimal /
data-density direction; no backend or new libraries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add GET /api/dashboard/summary aggregating pipeline status, category
distribution, publish summary, and outperformers in one call. Rewrite
dashboard.html with 5 KPI cards, pipeline funnel, publish status, and
category/source-format breakdowns (CSS bars, no chart lib).
Backend: ChannelVideoRepository counts (shorts/uncategorized),
PublishPackageRepository.countByStatus, pipelineStats shorts/longForm,
CategoryService.distribution, PublishService.dashboardSummary, new
DashboardService + DashboardApiController.
Fix: PublishService.list(null) hit UnsupportedOperationException because
findAll(Sort) uses Criteria, which rejects nullsLast precedence. Route the
no-status path through a @Query method so Sort is appended as HQL ORDER BY
(supports NULLS LAST). Also fixes the latent bug in /api/v1/publish all-list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflect removal of Video/YtVideo/Opal: ChannelVideo is now the single
video master with the collect->curate->rework->publish pipeline. Update
external-integration section (Python transcript via ChannelService,
YouTube API via YoutubeSearchService; Google Docs/Opal gone) and the
security note (OAuth creds now unused, gitignored).
The legacy Opal content pipeline (YtVideo + ScriptGen + OpalDraft/Final/
FinalAsset, driven by AnalysisWorkflowService via hardcoded Google Docs)
is no longer used. The active flow is ChannelVideo: collect -> curate
(board) -> rework -> publish.
Removed:
- service: AnalysisWorkflowService, YtVideoService, external/ExternalApiService(+Impl/Stub)
- web: YtVideoController, VideoActionController (/api/videos), video_detail.html
- web/dto: Video{Response,SearchCondition,AddRequest,DetailResponse},
FinalAssetResponse, OpalDraftResponse, DraftGenerateRequest
- domain/video: YtVideo, YtVideoRepository, dto/Video{List,Detail}Response
- domain/script: ScriptGen(+Repository)
- domain/opal: OpalDraft/OpalFinal/OpalFinalAsset(+Repositories, dto)
Preserved the active YouTube search by extracting searchYoutubeVideos()
into a new dedicated YoutubeSearchService (no Opal deps); rewired
YoutubeSearchApiController. WebController drops the /videos/{id} Opal
detail route + YtVideoService dependency.
DB note: ddl-auto=update never drops tables, so yt_video / scriptgen /
opal_* remain as orphaned tables (harmless, no data loss). Verified by
clean compileJava + reference sweep across java/html/yml.
Video/VideoRepository/VideoService/VideoController (table 'videos',
/api/v1/videos) were a legacy read-model with zero cross-package
references. The active flows use ChannelVideo (collection→rework→publish)
and YtVideo (Opal pipeline). Verified by clean compileJava.