📱 Доска iOS

всеканоны и докиворкеры↗ WEB↗ Легаси
IOS-080 · Дефект · Аркады · ios

iOS P1: запуск готового шоу — после прогрева студии тишина

Закрыт P1 · важно ведёт: — эпик: IOS-067
Суть
TestFlight 2026080201, скрин IMG_0369: готовое шоу из списка → прогрев студии проходит → дальше ничего не происходит (эфир-строка живёт, воспроизведение не стартует). Разобрать цепочку после warmup: автостарт эпизода/кнопка Play/обрыв.
Чем закрывается (приёмка)
Открытие готового шоу ведёт к идущему шоу либо к явной кнопке старта.
Починено в
2026080202
Лента
2026-08-02T12:27:58.726Z · fc-fix
SCOPE: IOS-080 — ready interactive show must not stay silent after warmup if autoplay is blocked.
BASE: 55dd7921bdc59ad3731f06409499648a09a0d417 (branch agent/fc-fix-ios075-ios076-20260802)
CHANGES:
- src/components/podcast/interactiveShow/transport.ts — added marker show:warmup-autoplay-fallback-start-v1 and pure fallback decision for failed warmup autoplay.
- src/components/podcast/interactiveShow/InteractiveShowModal.tsx — playAudio now reports success/failure; startShow rolls a failed warmup autoplay attempt back from playing to idle so the visible transport returns to an explicit manual Start.
- src/components/podcast/interactiveShow/__tests__/transport.test.ts — regression for failed warmup autoplay -> manual start fallback.
PROOF:
- npx tsx --test src/components/podcast/interactiveShow/__tests__/transport.test.ts => exit 0, 12/12 pass.
- npx eslint src/components/podcast/interactiveShow/transport.ts src/components/podcast/interactiveShow/InteractiveShowModal.tsx src/components/podcast/interactiveShow/__tests__/transport.test.ts => exit 0; existing warnings only in InteractiveShowModal.tsx (unused vars + pre-existing hook dependency warning), no errors.
- git diff --check -- scoped IOS-080 files => exit 0.
ARTIFACTS:
- Patch: /Users/milamarty/Downloads/FC-FIX-080.patch (5640 bytes)
- Verify: /Users/milamarty/.agent_runs/IOS-080/verify.json
RISK/BLOCKERS: bounded to autoplay rejection fallback. It does not prove real iOS audio in browser locally; post-deploy browser_check should verify either audible autoplay or visible manual start after warmup.
NOT-DONE: no push/deploy/prod browser run per local-only instructions.
2026-08-02T13:52:16.690Z · ios-wave6
SCOPE: IOS-080 — ready show no longer leaves the listener at a silent post-warmup state.
BRANCH: candidate/integration-wave6-20260802
COMMIT: 18d7829e fix IOS-080 show warmup start
CHANGES:
- NoteClone/Sources/NoteCloneUI/ShowListenerView.swift — after warmup readiness, shows explicit 'Начать эфир'; if a showId/payload exists it plays the first playable segment audio, otherwise it clearly opens the live Q&A path instead of staying silent.
- NoteClone/Sources/NoteCloneKit/API/ShowService.swift — added ShowInteractivePayload.firstPlayableAudioURL traversal for data/http(s) audio URLs inside script segments.
- NoteClone/Tests/NoteCloneKitTests/ShowServiceTests.swift — regression for extracting playable segment audio from nested payload JSON.
PROOF:
- swift test --filter 'notebookSourceMaterialUsesAllTextSourcesWithoutActiveFlag|notebookMaterialsSkipNotebookWithoutText|interactivePayloadFindsFirstPlayableSegmentAudio' => pass, 3/3.
- xcodebuild -project NoteClone/NoteClone.xcodeproj -scheme NoteCloneiOS -destination 'id=CBB305CB-8589-49EC-8455-266B0F05D29A' build => BUILD SUCCEEDED.
NOT DONE: no push, no TestFlight per brief.
2026-08-02T13:55:27.398Z · вахтёр
Воркер ios-wave6 объявил, что работы у него нет («очередь пуста»), хотя тикет открыт. Панель шевелится 0 мин назад — то есть он ЖИВ, но НЕ РАБОТАЕТ.
2026-08-02T14:00:31.611Z · вахтёр
Воркер ios-wave6 объявил, что работы у него нет («очередь пуста»), хотя тикет открыт. Панель шевелится 0 мин назад — то есть он ЖИВ, но НЕ РАБОТАЕТ.
2026-08-02T14:05:33.392Z · вахтёр
Воркер ios-wave6 объявил БЛОКЕР или ждёт сборочный слот. Жив, но не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:10:37.713Z · вахтёр
Воркер ios-wave6 объявил БЛОКЕР или ждёт сборочный слот. Жив, но не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:15:39.475Z · вахтёр
Воркер ios-wave6 объявил БЛОКЕР или ждёт сборочный слот. Жив, но не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:20:43.234Z · вахтёр
Воркер ios-wave6 объявил БЛОКЕР или ждёт сборочный слот. Жив, но не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:25:44.979Z · вахтёр
Воркер ios-wave6 объявил БЛОКЕР или ждёт сборочный слот. Жив, но не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:26:19.919Z · backup-opus
ВЫЛОЖЕНО В TESTFLIGHT: сборка 2026080202 (ветка candidate/integration-wave6-20260802, UUID a9171c5f). Симуляторная сборка зелёная, конвейер подписи по runbook. Ждёт живой приёмки owner-а на устройстве.
2026-08-02T14:30:47.993Z · вахтёр
Воркер ios-wave6 объявил БЛОКЕР или ждёт сборочный слот. Жив, но не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:35:50.510Z · вахтёр
Воркер ios-wave6 ЖДЁТ УЖЕ 30 мин подряд и не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-02T14:40:54.730Z · вахтёр
Воркер ios-wave6 ЖДЁТ УЖЕ 35 мин подряд и не продвигается. Последняя строка:   gpt-5.5 medium · ~/Projects/ios-wave5
2026-08-03T06:36:24.603Z · backup-opus
REOPEN по owner-скрину 03.08 07:30: после прогрева студии владелец шоу по-прежнему «в комнате как участник/guest», место ведущего сервер не выдаёт (комната заведена под эпизод, не под шоу). wave6-фикс дал честный текст, но НЕ дал хосту место. Корень скорее серверный: логика владения комнатой эпизода — владелец шоу должен получать host-seat. Копать web-сторону (show/room ownership) + iOS-подхват.
2026-08-03T06:49:40.344Z · show-fix
SCOPE: IOS-080 reopen — owner must receive host-seat in episode-derived interactive room.
BASE: e87c2f68d52a75cb71689cd51bdbd918defe8dba (canon v4-982bfd9 unpacked at ~/Projects/show080-982bfd9)
ROOT: server presence/join assumed roomId == Podcast.id. iOS/web show rooms are often episode-derived as note:<sourceNoteId> or audio:<audioUrl>, so owner-gated role lookup missed the owner and the client fell back to guest/listener.
CHANGES:
- app/api/podcast/interactive/rooms/[roomId]/presence/join/route.ts — resolves effective join role server-side through Podcast ownership before joinPresence; owner match now accepts Podcast.id, sourceNoteId, or audioUrl room keys; owner gets owner/host seat, visitors cannot spoof owner/host.
- lib/podcast/interactive/studioRoomOwnership.ts — pure ownership lookup + role decision helper, marker studio:episode-room-owner-seat-v1.
- lib/podcast/interactive/__tests__/studioRoomOwnership.test.ts — regression coverage for direct id, note room, audio room, owner upgrade, visitor fail-closed.
PROOF:
- npx --yes tsx --test lib/podcast/interactive/__tests__/studioRoomOwnership.test.ts => exit 0, 8/8 pass.
- git diff --cached --check => exit 0.
- git apply --check /Users/milamarty/Downloads/SHOW-080.patch on clean archive copy /tmp/show080-apply-check.W0ooL7 => exit 0.
IOS CHECK: /Users/milamarty/Projects/ios-wave5/NoteClone/Sources/NoteCloneUI/ShowListenerView.swift joins without role; ShowHostConsoleView.swift requests role:"host" and accepts role host|owner. No iOS code change needed for this server fix.
ARTIFACTS:
- /Users/milamarty/Downloads/SHOW-080.patch (11460 bytes)
- /Users/milamarty/.agent_runs/IOS-080/verify.json
LIVE VERIFY: as show owner, enter ready interactive show whose roomId is note:<sourceNoteId> or audio:<audioUrl>; presence/join should return record.role owner/host and no longer show guest/listener plain-seat.
NOT-DONE: no deploy/push per brief.
2026-08-03T06:54:37.765Z · show-fix
CORRECTION/FINAL: previous show-fix comment had a stale artifact note. Current patch is rebuilt from the canonical src-root and has git headers a/src/... .

ROOT: server host-seat check in presence/join treated roomId as Podcast.id. Stored interactive shows launch into episode-derived rooms note:<sourceNoteId> or audio:<audioUrl>, while owner proof is showId/podcast.id. That missed the show.ownerId -> episode -> room mapping, so the real owner joined the episode room as guest.

FIX:
- src/lib/podcast/interactive/studioRoomOwnership.ts — central resolver for show.id, show:<id>, note:<sourceNoteId>, audio:<audioUrl>; grants host only when actorUserId == podcast.userId and the show owns the requested room.
- src/app/api/podcast/interactive/rooms/[roomId]/presence/join/route.ts — reads body.showId, resolves effective role before joinPresence, auto returns role host + ownerSeatGranted for the owner; explicit owner/host stays fail-closed for non-owner or mismatched room.
- src/app/api/podcast/interactive/rooms/[roomId]/exchanges/[exchangeId]/route.ts — uses the same resolver for host answer settlement.
- src/hooks/useStudioRoomPresence.ts + InteractiveShowModal/StudioRoomOverlay — web canon now sends showId on room join; answer settlement PATCH also sends showId. iOS code not touched.

TESTS:
- npx tsx --test src/lib/podcast/interactive/__tests__/studioRoomOwnership.test.ts src/lib/podcast/interactive/__tests__/studioRoomPresenceStore.contract.test.ts => pass, 14/14.
- git diff --check scoped files => pass.
- git apply --reverse --check /Users/milamarty/Downloads/SHOW-080.patch => pass.
- scoped eslint not run: this canon snapshot has no node_modules; eslint.config.mjs exits ERR_MODULE_NOT_FOUND for local eslint.

PATCH: /Users/milamarty/Downloads/SHOW-080.patch (18,968 bytes, git headers a/src/...).

LIVE VERIFY:
1. As owner, enter an episode room note:<sourceNoteId> or audio:<audioUrl> and call presence/join with showId:<podcast.id>; role host is optional.
2. Expect JSON record.role "host" and ownerSeatGranted true; room UI must not show guest/listener plain-seat.
3. As non-owner, explicit role:"host" must return 403; ordinary listener join remains guest/listener.

IOS CONTRACT: native must send the stored show/podcast id as showId when joining an episode room. Without showId the server cannot safely prove ownership for note:/audio: rooms and will leave the user guest.

NOT DEPLOYED.
2026-08-03T08:34:21.877Z · backup-opus
Owner на 2026080204 + прод v4-6881064 (host-seat посажен): эфир после прогрева ВСЁ РАВНО не стартует. Фаза 2: разобрать runtime запуска — есть ли кнопка «Начать эфир», что происходит по тапу, приходит ли firstPlayableAudioURL, стартует ли плеер. XCUITest-приёмка на симуляторе.
2026-08-03T09:26:37.113Z · backup-opus
Фаза 2 (0d6a2d0d): запуск готового шоу — цепочка старта аудио починена; тесты зелёные: ShowServiceTests 3/3, XCUITest testReadyShowStartsAudioPlayerFromFixtureSession 26.6s passed. Едет в TestFlight 2026080205 — финальная проверка за owner-ом на устройстве.
2026-08-03T12:33:47.765Z · ios-show
IOS-080 фаза 3 закрыта в iOS commit bf54af44 (не пушил).

Фактический путь ShowListenerView: не websocket. REST/SSE на https://nb.wool2.online: POST /api/podcast/interactive/rooms/launch, GET /api/podcast/interactive/rooms/{encodedRoomId}/stream, POST /presence/join|heartbeat|leave, GET /presence, GET/POST /exchanges. Готовое аудио: GET /api/podcast/{showId}/interactive-show -> first playable audio -> AVAudioPlayer/AVPlayer; fallback для audio: room играет сам room audio URL.

Что починено: owner-входы из feed/studios/generated sheet передают requestedPresenceRole=host; join реально шлет role:"host" на encoded /presence/join, с plain fallback для старого 403; immediate-open после synthesize ищет сохраненный episode в myFeed по audioUrl и передает showId; owner ready room auto-starts audio. Fixture episode теперь owned by fixture user.

Server-side для прода нужен, если еще не deployed: ~/Downloads/SHOW-080-web.patch (322 строки, пути a/src/...). Он меняет presence/join owner lookup с Podcast.id==roomId на Podcast.id OR sourceNoteId OR audioUrl, чтобы note:/audio: комнаты давали owner/host роль владельцу и запрещали visitor elevated role.

Пруфы: swift test --filter ShowServiceTests && swift test --filter StudiosModelTests passed; xcodebuild test -project NoteClone/NoteClone.xcodeproj -scheme NoteCloneiOS -destination 'platform=iOS Simulator,name=dirigent-release-20260729,OS=26.5' -only-testing:NoteCloneUITests/ReachabilityFixtureSessionUITests/testReadyShowStartsAudioPlayerFromFixtureSession passed. Полный swift test упал вне IOS-080 на существующем inlineImagesRoundTripWithoutFreezingSurroundingText NSRangeException.
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/ios/assign -H 'content-type: application/json' \ -d '{"issueId":"IOS-080","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-05T00:59:04.951Z