• POST /api/auth/register, POST /api/auth/login, POST /api/auth/logout,
    and a debug GET /api/auth/me (T19's own acceptance criteria calls for
    it explicitly). Passwords hashed with node:crypto's scryptSync (salt
    stored alongside the hash, verified with timingSafeEqual) — still zero
    new dependencies.
    
    Sessions live in a separate user_session cookie from admin_session
    (T6) — the two are unrelated systems, confirmed by a test that a user
    session can't unlock /api/admin/*. Login and "email not registered"
    return the identical 401 body so the endpoint can't be used to
    enumerate registered emails.
    
    Every test file that spawns server.js in an isolated tmpdir now also
    copies db.js alongside it (dance-channel/dressup-generate/dressup-crud/
    admin-pages-flow/uploads-route .test.js) — server.js requires db.js
    unconditionally now, so those child processes were crashing silently
    without it.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_011Deddn3sM3oQnmvqWSMb4y
    yibo authored
     
    Browse File »
  • Backend (server.js):
    - T11: dance.html gets its own schedule/queue state machine, persisted to
      dance-state.json (gitignored, same as live-state.json). Per MVCP M.8,
      the schedule stores videoId references rather than inlined clip
      snapshots like live's does, joining back to library.json at read time.
      Generalized pickBestIndexByStatus/extracted pickLeastPlayedClip so both
      channels' schedulers share the same selection logic instead of forking
      it; live's random-fill pool now excludes channel:"dance" clips (and
      vice versa) so the two channels' filler picks stay independent, which
      wasn't true before this (T8's dance clips could have leaked into
      live.html's filler).
    - T12: GET /api/dance/wardrobe — public, unauthenticated list of active
      garments.
    - T15: POST /api/dance/pick {garmentId, nickname?} — free path, always
      uses the configured default character, reuses submitGeneration (T8/T9)
      whole including its M.7 dedupe cache, then hands the result to
      danceQueue. In-flight generations are reconciled against generationJobs
      on each schedule tick rather than via a promise chain, so a pick
      request survives a server restart the same way T8's own jobs do.
    - T17: separate danceViewers map and POST /api/dance/queue/vote/:id, so
      viewer counts and votes don't mix with live.html's.
    - SCHEDULE_TICK_MS env var makes both channels' tick interval
      configurable so tests don't wait out a real 2s.
    
    Frontend:
    - dance.html (T13/T14/T16): reuses live.html's visual language via
      live.css, adds dance.css for the 9:16 boxed player and wardrobe grid.
      Left column is a 衣柜/传衣服/传人 tab set (only 衣柜 is wired up —
      the other two need accounts/credits, dev-plan-2.md 第三/四期); right
      column shows the queue with vote buttons instead of live.html's chat.
      Age-confirmation gate (5.1节) blocks the page on first visit via a
      cookie, non-dismissable by clicking the backdrop.
    
    Tests: dance-channel.test.js spins up a mock H3 Console (same approach
    as dressup-generate.test.js) to exercise wardrobe listing, pick
    rejection/happy-path/dedupe, vote, and independent viewer counts
    end to end.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse File »