Skip to content

feat: Actress Showcase mode + precise match hero card (v0.7.3)#25

Closed
slive777 wants to merge 30 commits intomainfrom
feature/44-actress-showcase
Closed

feat: Actress Showcase mode + precise match hero card (v0.7.3)#25
slive777 wants to merge 30 commits intomainfrom
feature/44-actress-showcase

Conversation

@slive777
Copy link
Copy Markdown
Owner

@slive777 slive777 commented Apr 12, 2026

Summary

  • Showcase 女優模式:toolbar toggle 切換影片 Grid ↔ 女優收藏 Grid,含搜尋、排序、CRUD 操作
  • 精準匹配 Hero Card:搜尋已收藏女優名 → ♥ 愛心 + hero card 顯示在 grid 上方,lightbox -1 sentinel 導航
  • GSAP 動畫全套:女優模式 crossfade、卡片 stagger entry、sort FLIP、hero card appear/leave
  • i18n 四語系 + Capabilities 同步

Phases

  • 44a: Actress list foundation (mode toggle + grid + lightbox + CRUD)
  • 44b: Precise match hero card (search bar heart + hero card + -1 sentinel navigation)
  • 44c: Card & grid polish (footer 3-col + dense grid + segmented toggle + search films)

Test plan

  • 2317 unit/integration tests passed (0 failures)
  • Capabilities count 23→24 (list_actresses added)
  • E2E: searchFromMetadata 已收藏 → ♥ + hero card
  • E2E: searchFromMetadata 未收藏 → ♡ → 點擊 → ♥ + hero card
  • E2E: 手打搜尋已收藏 → ♥ + hero card
  • E2E: 手打搜尋未收藏 → 無愛心無 hero card
  • E2E: Hero card lightbox ↔ 影片 lightbox 導航
  • E2E: ESC / 搜尋清空 → 全部重置
  • E2E: toggleActressMode round-trip → state 清除
  • E2E: searchActressFilms → hero card 出現
  • E2E: Desktop hero card 寬度 = grid 單格寬度

🤖 Generated with Claude Code

peace and others added 29 commits April 12, 2026 19:48
CHANGELOG.md 從 1270 行縮減至 340 行(-73%):
0.6.x~0.7.x 完整保留,0.1.x~0.5.x 壓縮為 5 段摘要。
完整歷史移至 CHANGELOG_ARCHIVE.md(962 行)。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ActressRepository.count_videos_for_actress() — json_each() exact match + json_valid() guard
- GET /api/actresses — list all favorites with video_count + created_at
- POST /api/actresses/{name}/rescrape — force re-fetch + DB upsert + photo refresh
- _actress_to_response() extended with video_count (default 0) + created_at
- 10 unit tests (list happy/empty/exact-match/dirty-data, rescrape happy/timeout/404/new-name)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lightbox

- Module-level _actresses[] / _filteredActresses[] (non-reactive pattern)
- 16 new Alpine state properties (showFavoriteActresses, sort/filter/lightbox state)
- _setLightboxIndex() mutual exclusion: clears currentLightboxActress + resets _videoChipsExpanded
- 6 core methods: toggleActressMode, loadActresses, applyActressFilterAndSort, sort/order/search
- 5 lightbox methods: _setActressLightboxIndex, open/close/prev/next with GSAP generation guards
- 6-key sort (video_count/name/created_at/age/height/cup) with null-last consistency
- handleKeydown actress lightbox dispatch
- saveState/restoreState for 3 actress keys (strict === true for mode)
- 41 frontend guard tests (TestShowcaseActressState)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tate

- Mode toggle button (bi-film/bi-person) with CD-8 disabled guard
- Dual search inputs: x-show video/actress with shared clear button
- Video toolbar controls conditional (x-show="!showFavoriteActresses")
- Actress toolbar controls: 6-option sort dropdown + order toggle
- x-if video/actress grid branches (CD-2: avoid Alpine tracking hidden arrays)
- Actress grid: paginatedActresses x-for + actress-card + data-flip-id
- Actress card: 3:4 portrait photo, no-photo placeholder, name + count badge
- Loading spinner + empty state (actressCount === 0)
- CSS .actress-card with design tokens + hover effects
- 14 frontend guard tests (TestShowcaseActressTemplate)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Shared lightbox DOM: x-if actress/video content branches (CD-3)
- 5-row actress metadata: cover, header (name+heart), core metadata (·-joined),
  aliases chips +N, info chips +N, URLs (http-only guard), action placeholders
- Chips +N: _chipsLimit (desktop 10/mobile 6), _visibleAliases/_aliasesOverflow,
  _visibleInfoChips/_infoChipsOverflow, _allInfoChips merge
- Video tag chips sync +N: _visibleVideoTags/_videoTagsOverflow
- Nav arrows dispatch on showFavoriteActresses (not currentLightboxActress)
- 9 new JS methods with null guards
- CSS: actress-lightbox-meta, lb-chips-more (shared), cover placeholder
- Fix lb-header test regression (precise class match, not substring)
- 11 frontend guard tests (TestShowcaseActressLightbox)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- [+ 新增] DaisyUI popover: input + confirm + Enter shortcut + loading spinner
- addFavoriteActress(): POST /favorite → 409 info/404 error/504 error/200 success toast
  push to _actresses + re-sort + close dropdown
- rescrapeActress(): POST /rescrape → Object.assign in-place + photo cache-bust
  sync _actresses array, lightbox stays open
- removeActress(): confirm → DELETE → splice _actresses + re-sort + close lightbox
- encodeURIComponent for Japanese names in URL path
- zh_TW.json: showcase.actress 14 keys + mode/search/sort/unit keys
- 7 frontend guard tests (TestShowcaseActressCRUD)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… FLIP

- animations.js: 4 selector changes (.av-card-preview → .av-card-preview, .actress-card)
  playEntry, capturePositions, playFlipReorder, playFlipFilter (playSettle unchanged)
- toggleActressMode: $nextTick crossfade + conditional playEntry for cache-hit
- loadActresses: $nextTick + rAF playEntry in success path
- Sort FLIP works via existing _sortWithFlip (no change needed after selector fix)
- Lightbox animations already wired in T4 (zero change)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- zh_TW: +2 keys (showcase.search.video, showcase.unit.films)
- zh_CN/en/ja: +26 keys each (showcase.actress.*, mode.*, search.*, sort.actress.*, unit.*)
- Capabilities: rescrape_actress tool (side_effect + confirmation_required, irreversible warning)
- TestShowcaseActressI18n: 26 parametrize keys × 4 locales = 104 sub-tests
- test_capabilities: count 22→23 + rescrape_actress in EXPECTED_TOOL_NAMES
- Full suite: 2276 passed, 0 failed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ate)

F1: videoCount > 0 guard → (videoCount > 0 || showFavoriteActresses)
    actress mode accessible with 0 indexed videos
F2: init() calls loadActresses() when restoring showFavoriteActresses=true
F3: photo cache-bust '&t=' → '?t=' (no existing query string)
F4: rescrapeActress() stale guard — array always updated, lightbox only if same actress
F4b: removeActress() stale guard — splice+re-sort always, closeActressLightbox only if same
F5: sort comparator 'created_at' → 'added_at' (matches template sort value)
F6: video loading/empty states add !showFavoriteActresses guard
F7: rescrapeActress() stale guard restructured — don't discard successful array update

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both endpoints called _actress_to_response without querying
count_videos_for_actress(), defaulting to 0. Cards now show
correct film count immediately after favorite/rescrape.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three-column footer (name / dynamic sort indicator / age) with
hover layer showing height · cup · BWH. Sort indicator follows
actressSort state; hover excludes age/birth per CD-7.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Row 6 text buttons, add rescrape + remove as .lb-action-btn
on lightbox cover with --danger red tint modifier and touch fallback
(@media pointer:coarse). Matches video lightbox cover-actions pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace ghost button with capsule toggle inside .spotlight-search.
Active mode gets glass highlight (backdrop-filter + semi-transparent).
Non-empty search disables opposite mode button. Input padding adjusted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add width:100% + box-sizing to .dropdown-item so clickable area fills
the panel. Add z-index:210 to .toolbar-dropdown-wrap to prevent
stacking issues with adjacent toolbar buttons. Bump min-width 160px.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dedicated .actress-grid (minmax 160px) replaces shared .showcase-grid
for denser card packing. _getActiveGrid() helper routes 6 querySelector
calls. playModeCrossfade gets actress selector. 480px breakpoint = 3col.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… mode

Design adjustment: .actress-grid minmax 160px→220px for visual
readability (spec/plan updated locally). Fix _sortWithFlip guard
to include actress mode. Fix toggleActressMode crossfade to use
self.mode instead of hardcoded 'grid'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…reels

Lightbox cover-actions: rescrape button → searchActressFilms() with
bi-camera-reels icon. Grid card: new .actress-card-overlay hover
button with same action. Flow: close lightbox → switch to video
mode → set search to actress name → animate filter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move .actress-card-overlay from center (inset:0) to bottom gradient
bar matching lightbox cover-actions pattern. Add :focus-within for
keyboard accessibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…okens

8a: --spotlight-width/--spotlight-left-slot/--spotlight-right-slot tokens
8b: showcase-toolbar flex→grid (1fr/680px/1fr) — search always page-center
8c: variant class .spotlight-search--mode-toggle replaces bare padding leak
Bonus: #ff6b6b→var(--color-error-hover), tailwind recompile, 4 guard tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add _checkPreciseActressMatch(term, source) with stale guard and
_actresses lazy-load flag. Wire into searchFromMetadata (metadata path)
and onSearchChange (manual path). 12 frontend lint guards added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ♥/♡ heart in search bar for precise actress match. Clickable ♡
on metadata path triggers POST /api/actresses/favorite with loading
state + toast feedback. 4 frontend lint guards + 2 i18n keys added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add av-card-preview hero-card template with photo/fallback/overlay/
footer-hover. Reset _heroCardImageError on actress change. Add
openHeroCardLightbox stub for T4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement openHeroCardLightbox with direct -1 assignment, prev/next
sentinel transitions, hasVisiblePrev/Next computed, handleKeydown
showFavoriteActresses fix, and removeActress x-show guard. 7 guards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add playHeroCardAppear in animations.js with reduced-motion fallback.
Trigger on precise match with is_favorite. Alpine x-transition:leave
for disappear. Lightbox animations reuse T4 wiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded "No Image" with t('common.no_image') in 3 places
across showcase + search templates. Add 4-locale key + 2 guard tests.
Full suite 2316 passed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0: _actress_to_response() now includes is_favorite: True — all
actresses in DB are favorites by definition. Fixes empty heart +
missing hero card.

P0: hero card max-width: 280px — prevents full-width stretch on
desktop when outside grid container.

P1: searchFromMetadata(term, type) — only 'actress' type triggers
_checkPreciseActressMatch; other metadata (maker/series/tag) clears
any stale match instead of creating false actress stubs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P0: searchActressFilms() now calls _checkPreciseActressMatch so hero
card appears when navigating from actress mode.

P1: test_list_with_actresses asserts is_favorite: True on every entry
to guard the API contract.

P2: hero card moved inside .showcase-grid as first child (matching
/search pattern) — grid auto-sizes width instead of fixed max-width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: toggleActressMode() now calls _clearPreciseMatch() when entering
actress mode, preventing hero card/heart from persisting with empty
search bar after mode round-trip. Guard test added.

P2: ja.json showcase.actress.favorited fixed from Chinese "収藏済み"
to Japanese "お気に入り済み".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump version 0.7.2→0.7.3. Add Phase 44 CHANGELOG entry. Add
list_actresses to capabilities manifest (24 tools). Add 2 guard
tests (playHeroCardAppear + searchFromMetadata actress type).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a9e146f74

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/routers/actress.py
Comment thread web/routers/actress.py
P1: evict _actress_cache entry before get_actress_profile in rescrape
handler, fulfilling the "bypass cache" contract.

P2: re-read actress from DB after save() in both favorite and rescrape
handlers so created_at is the real SQLite timestamp, not null.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@slive777
Copy link
Copy Markdown
Owner Author

Codex Review Response (c290a1f)

P1 — rescrape cache bypass: Fixed. _actress_cache.pop(_normalize_actress_name(name), None) evicts the cache entry before calling get_actress_profile(), so rescrape always makes fresh network requests.

P2 — created_at null: Fixed. Both add_favorite and rescrape_actress now call repo.get_by_name() after save() to re-read the persisted object with the real SQLite created_at timestamp.

@slive777 slive777 closed this Apr 12, 2026
@slive777 slive777 deleted the feature/44-actress-showcase branch April 15, 2026 17:07
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.

1 participant