Commit Graph

26 Commits

Author SHA1 Message Date
fa7cec7f14 docs: add subtitle timeline studio design spec
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>
2026-06-12 15:34:53 +09:00
hehih
aaf8901f13 chore: untrack stale .playwright-mcp snapshots (already in .git/info/exclude)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:06:33 +09:00
hehih
c08a9bfa93 chore: ignore tooling scratch artifacts; untrack git-ignored .claude files
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>
2026-05-31 17:00:39 +09:00
hehih
9edd4c5d97 fix(publish): dashboard 'recent' = latest-updated 5, stable status order
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>
2026-05-31 16:40:58 +09:00
hehih
fd8403438e chore(security): externalize secrets to git-ignored application-local.yml
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>
2026-05-31 16:08:26 +09:00
2cdae03998 test: add unit test infrastructure and core-logic tests
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>
2026-05-31 13:37:52 +09:00
9828918b97 style: dedupe to shared components (publish badge, filter selects, gap token)
- 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>
2026-05-31 10:45:15 +09:00
152dc0bae4 fix(discover): cast nullable params so Postgres can infer type
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>
2026-05-31 09:56:28 +09:00
632a2d4f8a fix: scope form focus rule, robust .hidden, drop unused shadow tokens, funnel amber stage 2026-05-31 01:38:08 +09:00
04891a7838 feat(collection): apply initial filters from URL params (dashboard deep-link target) 2026-05-31 01:01:18 +09:00
8fe90e431f fix(dashboard): null-ratio badge color, escape categoryId, robust refresh selector, animate once 2026-05-31 00:59:20 +09:00
2d6c962567 feat(dashboard): count-up, 3-tone bars, rank/ratio badges, click-through deep links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 00:53:25 +09:00
5ec04eaa60 style(css): refine shared components (buttons, badge, bar, forms, table, skeleton) 2026-05-31 00:48:14 +09:00
390037efe2 style(css): backfill utility classes used across templates 2026-05-31 00:45:05 +09:00
7f2227133d style(css): add design tokens (primary-rgb, warning, spacing, shadow, tabular-nums) 2026-05-31 00:29:55 +09:00
15d8ec0b88 docs: add all-pages design uplift implementation plan
Phase 1 (CSS tokens, utility backfill, shared components) with complete
CSS; Phase 2 (dashboard visuals + deep links, collection URL filters,
per-page sweep). Verification via bootRun + browser observation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:25:53 +09:00
d8c63724b2 docs: add all-pages design/UX uplift spec
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>
2026-05-31 00:18:42 +09:00
2ec3915789 feat(dashboard): add full-set pipeline summary with single endpoint
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>
2026-05-30 22:31:21 +09:00
hehih
92ae575646 docs: add dashboard full-set enhancement spec 2026-05-30 22:02:10 +09:00
hehih
f232d20f46 feat(discover): add discovery page for finding rework candidates
New /discover page surfaces collected ChannelVideos for picking rework
targets, filtered by period/min-ratio/source/shorts/unprocessed and
sorted by ratio/velocity/views/recency (EXCLUDED always hidden, null
ratios sorted last). Reuses existing curation endpoints for row actions
(status, bookmark).

- ChannelVideoRepository.discover() JPQL query
- ChannelVideoCurationService.discover() (defaults, limit cap, nullsLast)
- GET /api/v1/channel-videos/discover endpoint
- /discover page route + discover.html + sidebar link
Verified by clean compileJava.
2026-05-30 21:51:28 +09:00
hehih
c69a02dae8 docs: add discovery page implementation plan 2026-05-30 21:48:03 +09:00
hehih
50afa4ae51 docs: add discovery page design spec 2026-05-30 21:44:13 +09:00
hehih
487dfc6d0f docs: update CLAUDE.md for ChannelVideo single-master architecture
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).
2026-05-30 19:59:38 +09:00
hehih
9bd7e80542 Retire Opal/YtVideo pipeline; ChannelVideo is the single video master
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.
2026-05-30 19:51:53 +09:00
hehih
e862498f96 Remove dead legacy Video model cluster
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.
2026-05-30 19:10:50 +09:00
hehih
da04dbe15c Baseline before video model consolidation 2026-05-30 18:56:21 +09:00