Skip to content

MOBILE-323: Embedded block content provider - #754

Merged
Vailence merged 9 commits into
feature/MOBILE-323-block-foundationfrom
feature/MOBILE-323-block-content-provider
Aug 12, 2026
Merged

MOBILE-323: Embedded block content provider#754
Vailence merged 9 commits into
feature/MOBILE-323-block-foundationfrom
feature/MOBILE-323-block-content-provider

Conversation

@Vailence

Copy link
Copy Markdown
Collaborator

Description

Вторая из четырёх частей встроенных блоков: провайдер контента — то, что переводит сообщения страницы в состояния блока.

Публичного API для хост-приложения здесь по-прежнему нет: контейнер и SwiftUI-обёртка в следующих частях.

⚠️ Стек поверх #751. База этого PR — feature/MOBILE-323-block-foundation, чтобы в диффе было видно только своё. После мерджа #751 базу переставлю на mission/stories.

Что внутри

EmbeddedBlockWebViewProvider — не рисует контент и не знает механик: спрашивает у резолвера, что стоит за id, разбирает core-слой сообщений, а действия сверх него отдаёт универсальному обработчику.

Готовность определяет только сама страница: ready — показываем, empty — показывать нечего. Молчащая страница готовой не становится — её добьёт бюджет ожидания у контейнера. Нулевая высота в ready значит сломанную вёрстку, потому что «показывать нечего» страница сообщает явным empty.

Исход попытки хранится явно, одним значением, а не пачкой флагов. Он переживает stop(), потому что это свойство страницы, а не факта нахождения в окне: уход блока с экрана не выбрасывает уже отрендеренную страницу, и возврат показывает её снова — без сети и без шиммера. При этом провал и empty страницу не убивают: она жива и может продолжать работать, поэтому известный исход служит и признаком того, что блока на экране больше нет.

EmbeddedBlockContentProviderFactory — резолвер и обработчик действий общие на все блоки, провайдер свой на каждый. Это и делает блоки с одинаковым id независимыми. Потребителей у фабрики два, и оба в следующей части: DI-регистрация и публичный init контейнера.

На что стоит смотреть

  • Действия из непоказанного блока не выполняются. Схлопнутый блок не убивает страницу — она жива и может досылать то, что запланировал её setTimeout. За невидимым блоком не стоит ни одного касания пользователя, а openUrl увёл бы человека из приложения на пустом месте. Проверено с обеих сторон: из показанного блока действия доходят, после empty, провала и нулевой высоты — нет, а новая попытка снова их принимает.
  • loadGeneration — резолв может ответить уже после stop() или после перезагрузки; по номеру попытки видно, что ответ относится к прошлой, и его надо выбросить.
  • Отключение старой страницы в reload() до cancel(): иначе её запоздавшие сообщения попадут в новую попытку.
  • Провалившийся блок получает новую попытку на возврате в окно — единственный ретрай, который у блока пока есть. Что при этом вёрстка хоста не прыгает, обеспечивает контейнер в части C.

Тесты

EmbeddedBlockWebViewProviderTests — весь путь блока без WebKit и без сети. Отдельно закреплено то, на что опираются соседние слои: после stop() провайдер молчит целиком, отрендеренная страница на возврате показывается как есть, выброшенная перезагрузкой страница не может доложить в новую попытку ни сообщением, ни провалом, ни через отладочную подмену готовности.

EmbeddedBlockContentProviderFactoryTests — написаны с нуля, на эталонной ветке их не было: фабрика иначе уехала бы без покрытия. Проверяют её собственный контракт — провайдер свой на каждый вызов, собран под запрошенный id, резолвер получает общий.

Счётчику живых блоков в каждом тесте свой id: он общий на процесс, иначе тесты, идущие параллельно, считали бы блоки друг друга.

Отдельным коммитом

MOBILE-323: Trim duplicated comments in the content provider — док класса пересказывал то, что уже сказано ниже по файлу, а мысль «уход из окна не выбрасывает страницу» шла трижды. Осталось два абзаца. Встроенные комментарии не тронуты: каждый объясняет отсутствующую строку, развилку или внешнюю причину.

Type of Change

  • New feature (non-breaking change which adds functionality)

Test Procedure

Mindbox scheme, iPhone 16 Pro simulator:

  • 1351 тест, 0 падений, 0 пропущенных, build succeeded — ровно +38 к части A (35 провайдера, 3 фабрики)
  • Новых предупреждений нет; в сборке остаются только два про конфиг SwiftLint (implicit_optional_initialization, function_name_whitespace), к этим изменениям отношения не имеющие

Pre-flight Checklist

  • Все тесты проходят локально
  • Новых предупреждений компилятора нет
  • Новая функциональность покрыта тестами
  • Публичный API хост-приложения не затронут
  • Merge-коммитов в ветке нет

Vailence added 6 commits August 10, 2026 17:00
Переводит сообщения страницы в состояния блока. Не рисует контент и не знает
механик: спрашивает у резолвера, что стоит за id, разбирает core-слой, а действия
сверх него отдаёт универсальному обработчику.

Готовность определяет только сама страница: ready — показываем, empty —
показывать нечего. Навигация судит исключительно о своём, молчащая страница
готовой не становится — её добьёт бюджет ожидания у контейнера. Нулевая высота в
ready значит сломанную вёрстку: «показывать нечего» страница сообщает явным empty.

Исход попытки хранится явно, а не пачкой флагов. Он переживает stop(), потому что
это свойство страницы, а не факта нахождения в окне: уход блока с экрана не
выбрасывает уже отрендеренную страницу, и возврат показывает её снова, без сети и
без шиммера. При этом провал и empty страницу не убивают — она жива и может
продолжать говорить, — поэтому известный исход служит и признаком того, что блока
на экране больше нет: действия от невидимого блока не выполняются. За ним не
стоит ни одного касания пользователя, а openUrl увёл бы человека из приложения
на пустом месте.

Экземпляр принадлежит одному контейнеру: ничего общего между контейнерами здесь
нет, это и делает возможными несколько независимых блоков с одним id. Номер
попытки отсекает резолв, доехавший уже после остановки или перезагрузки.

Счётчик живых блоков на id — диагностика, а не механика: два блока с одним id
законны, но чаще это скопированный id или переиспользованная ячейка, а у обоих
случаев нет симптомов кроме «блок оказался не там, где ждали».
Собирает провайдер под конкретный блок: резолвер и обработчик действий общие на
все блоки, а провайдер — свой на каждый. Это и делает блоки с одинаковым id
независимыми друг от друга.

Потребителей у фабрики появится два, и оба в следующей части: DI-регистрация и
публичный init контейнера. Здесь она едет вместе с провайдером, потому что
описывает его модель владения, а не способ его достать.
Дописывает моки до среза этой части: страница без WebKit, фабрика страниц со
счётчиком созданных, резолвер с отложенным ответом, обработчик действий и общая
заготовка провайдера со всеми подменёнными зависимостями.

Резолвер умеет держать ответ до отдельной команды: так проверяется резолв,
доехавший уже после остановки или перезагрузки блока. Фабрика считает страницы,
потому что перезагрузка обязана создать новую, а возврат блока в окно — нет.
Весь путь блока без WebKit и без сети: резолв, показ по ready, пустой блок,
сломанная нулевая высота, провал загрузки, действия страницы, отладочная подмена
готовности, остановка и перезапуск, перезагрузка.

Отдельно закреплено то, на что опираются соседние слои: после stop() провайдер
молчит целиком — иначе контейнер не смог бы свернуть просроченный блок; уже
отрендеренная страница на возврате в окно показывается как есть, без сети и
шиммера, а блок, который показать не удалось, получает новую попытку; выброшенная
перезагрузкой страница не может доложить в новую попытку ни сообщением, ни
провалом, ни через подмену готовности.

Действия проверяются с обеих сторон: из показанного блока доходят до обработчика,
из схлопнутого — нет, ни после empty, ни после провала, ни после нулевой высоты, —
а новая попытка снова их принимает.

Счётчику живых блоков в каждом тесте свой id: он общий на процесс, иначе тесты,
идущие параллельно, считали бы блоки друг друга.
Фабрика приехала без тестов, а её обещание — то, на чём держится независимость
блоков с одинаковым id: провайдер свой на каждый блок, резолвер общий на все.
Проверяется и то и другое, плюс что провайдер собран под запрошенный id.

Резолвер в тестах отвечает «пусто»: страницу для такого блока не создают, поэтому
настоящий вебвью фабрике здесь не нужен.
Док класса пересказывал то, что уже сказано ниже по файлу: правила готовности —
у apply(height:) и handleLoadFinish, владение высотой — у heightChanged, а мысль
«уход из окна не выбрасывает страницу» шла трижды — в доке класса, в доке
свойства page и во встроенном комментарии в start(). Осталось два абзаца: чем
провайдер является и что после stop() он обязан молчать — это межтиповой
инвариант, из одного файла его не видно.

Убраны и три дока, ушедшие из своей ответственности или пересказывавшие подпись:
contentView рассказывал, как контейнер растягивает вью; второй абзац reload() —
про плейсхолдер и события хосту; isShown повторял собственное имя.

Встроенные комментарии не тронуты: каждый объясняет отсутствующую строку,
развилку или внешнюю причину — то, чего в коде не прочитать.
@Vailence
Vailence requested a review from justSmK August 10, 2026 12:36
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
TestsPassed ✅SkippedFailedTime ⏱
Unit tests report1415 ran1415 ✅1m 48s 394ms

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds the “content provider” layer for Embedded Blocks: a per-container EmbeddedBlockWebViewProvider that resolves block id → web content, translates core page messages into container states, and routes non-core messages to a shared action handler. Also introduces a factory to create providers per block instance and adds comprehensive unit tests for both the provider and the factory.

Changes:

  • Introduced EmbeddedBlockWebViewProvider to manage lifecycle (start/stop/reload), readiness/empty/failure outcomes, and action routing.
  • Added EmbeddedBlockContentProviderFactory to create independent providers per block while sharing resolver/action handler.
  • Added new test suites and supporting mocks to validate provider/factory contracts, including reload/stop generation handling and live-block counting.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift Implements the embedded block web content provider: state transitions, action routing, reload/stop/start behavior, and live-count diagnostics.
Mindbox/EmbeddedBlocks/Container/EmbeddedBlockContentProviderFactory.swift Adds a factory that produces a new provider per block while reusing shared resolver and action handler.
MindboxTests/EmbeddedBlocks/EmbeddedBlockWebViewProviderTests.swift Adds extensive unit tests covering provider lifecycle, readiness/empty/failure behavior, action routing, reload semantics, and live-count behavior.
MindboxTests/EmbeddedBlocks/EmbeddedBlockContentProviderFactoryTests.swift Adds tests for factory guarantees: per-call provider uniqueness, id binding, and shared resolver usage.
MindboxTests/EmbeddedBlocks/EmbeddedBlockMocks.swift Extends test mocks and a test bed to support provider/factory test scenarios without WebKit or network.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift

@justSmK justSmK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Самая чистая часть стека. Разделение «страница знает про навигацию, провайдер про состояния» удачное, 35 тестов на 280 строк кода — хорошо. Блокеров нет.

Собрал все четыре ветки в один worktree (свелись чисто, конфликт только в project.pbxproj), собрал и прогнал тесты. С одной правкой (см. блокер в #755) сборка проходит: 1407 тестов, 0 падений.

Про порядок мержа стека. 754 и 755 не содержат двух последних коммитов фундамента (42b04e22, 250447cf), а 756 не содержит de08d916. Мержить надо строго по порядку с ребейзами.

Отдельно: 250447cf переформатировал project.pbxproj примерно на 150 строк. При сведении стека это гарантированный конфликт — я его получил, когда собирал ветки вместе локально. Лучше решить заранее, а не в момент мержа.

Comment thread Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift
Comment thread Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift
Comment thread Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift
Comment thread Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift Outdated
@Vailence
Vailence requested a review from justSmK August 11, 2026 13:35

@justSmK justSmK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Повторное ревью. Собрал и прогнал тесты на d7b9665c1354 теста, 0 падений.

Закрыл свои треды

  • Шумный лог на каждый блок — убран, остался только сигнал про count > 1. Ровно то, что предлагал.
  • Главный поток — guard стоит и на входе в resolve, и на колбэке, плюс 6 новых тестов на многопоточность. Эта версия полнее, чем в #751.

По вопросу «нужен ли вообще счётчик liveBlocks»: раз по DoD решили, что блоки с одним id — независимые сущности и это законно, диагностика описывает штатный сценарий. Я бы либо убрал, либо понизил уровень. Чутьё верное.

Ветка не перебазирована

merge-base(#751, #754) = 51956ac7, то есть ветка стоит на фундаменте до трёх его последних коммитов.

EmbeddedBlockResolver.swift правится в обеих ветках по-разному: #751 перевёл файл на английский и вынес finish(), здесь остался русский плюс onMain() и guard на входе. Это гарантированный конфликт по всему файлу, и в нём легко потерять именно то, что здесь сделано лучше — guard на входе в resolve. То же с EmbeddedBlockResolverTests.swift.

Просьба: перебазировать на текущий фундамент до мержа и при разрешении конфликта оставить версию отсюда.

Мелочь того же происхождения: появилась пустая строка между doc-комментарием и typealias EmbeddedBlockContentLoading, из-за чего комментарий отвалился от объявления. И из доки протокола пропала строка про главный поток, хотя в #751 она сохранена и переведена — после ребейза правильная версия оттуда.

Comment thread Mindbox/EmbeddedBlocks/WebView/EmbeddedBlockWebViewProvider.swift
@Vailence
Vailence merged commit ac03b30 into feature/MOBILE-323-block-foundation Aug 12, 2026
5 checks passed
@Vailence
Vailence deleted the feature/MOBILE-323-block-content-provider branch August 12, 2026 09:02
Vailence added a commit that referenced this pull request Aug 12, 2026
* MOBILE-323: Add the embedded block content provider

Переводит сообщения страницы в состояния блока. Не рисует контент и не знает
механик: спрашивает у резолвера, что стоит за id, разбирает core-слой, а действия
сверх него отдаёт универсальному обработчику.

Готовность определяет только сама страница: ready — показываем, empty —
показывать нечего. Навигация судит исключительно о своём, молчащая страница
готовой не становится — её добьёт бюджет ожидания у контейнера. Нулевая высота в
ready значит сломанную вёрстку: «показывать нечего» страница сообщает явным empty.

Исход попытки хранится явно, а не пачкой флагов. Он переживает stop(), потому что
это свойство страницы, а не факта нахождения в окне: уход блока с экрана не
выбрасывает уже отрендеренную страницу, и возврат показывает её снова, без сети и
без шиммера. При этом провал и empty страницу не убивают — она жива и может
продолжать говорить, — поэтому известный исход служит и признаком того, что блока
на экране больше нет: действия от невидимого блока не выполняются. За ним не
стоит ни одного касания пользователя, а openUrl увёл бы человека из приложения
на пустом месте.

Экземпляр принадлежит одному контейнеру: ничего общего между контейнерами здесь
нет, это и делает возможными несколько независимых блоков с одним id. Номер
попытки отсекает резолв, доехавший уже после остановки или перезагрузки.

Счётчик живых блоков на id — диагностика, а не механика: два блока с одним id
законны, но чаще это скопированный id или переиспользованная ячейка, а у обоих
случаев нет симптомов кроме «блок оказался не там, где ждали».

* MOBILE-323: Add the content provider factory

Собирает провайдер под конкретный блок: резолвер и обработчик действий общие на
все блоки, а провайдер — свой на каждый. Это и делает блоки с одинаковым id
независимыми друг от друга.

Потребителей у фабрики появится два, и оба в следующей части: DI-регистрация и
публичный init контейнера. Здесь она едет вместе с провайдером, потому что
описывает его модель владения, а не способ его достать.

* MOBILE-323: Add test doubles for the block content provider

Дописывает моки до среза этой части: страница без WebKit, фабрика страниц со
счётчиком созданных, резолвер с отложенным ответом, обработчик действий и общая
заготовка провайдера со всеми подменёнными зависимостями.

Резолвер умеет держать ответ до отдельной команды: так проверяется резолв,
доехавший уже после остановки или перезагрузки блока. Фабрика считает страницы,
потому что перезагрузка обязана создать новую, а возврат блока в окно — нет.

* MOBILE-323: Add tests for the embedded block content provider

Весь путь блока без WebKit и без сети: резолв, показ по ready, пустой блок,
сломанная нулевая высота, провал загрузки, действия страницы, отладочная подмена
готовности, остановка и перезапуск, перезагрузка.

Отдельно закреплено то, на что опираются соседние слои: после stop() провайдер
молчит целиком — иначе контейнер не смог бы свернуть просроченный блок; уже
отрендеренная страница на возврате в окно показывается как есть, без сети и
шиммера, а блок, который показать не удалось, получает новую попытку; выброшенная
перезагрузкой страница не может доложить в новую попытку ни сообщением, ни
провалом, ни через подмену готовности.

Действия проверяются с обеих сторон: из показанного блока доходят до обработчика,
из схлопнутого — нет, ни после empty, ни после провала, ни после нулевой высоты, —
а новая попытка снова их принимает.

Счётчику живых блоков в каждом тесте свой id: он общий на процесс, иначе тесты,
идущие параллельно, считали бы блоки друг друга.

* MOBILE-323: Add tests for the content provider factory

Фабрика приехала без тестов, а её обещание — то, на чём держится независимость
блоков с одинаковым id: провайдер свой на каждый блок, резолвер общий на все.
Проверяется и то и другое, плюс что провайдер собран под запрошенный id.

Резолвер в тестах отвечает «пусто»: страницу для такого блока не создают, поэтому
настоящий вебвью фабрике здесь не нужен.

* MOBILE-323: Trim duplicated comments in the content provider

Док класса пересказывал то, что уже сказано ниже по файлу: правила готовности —
у apply(height:) и handleLoadFinish, владение высотой — у heightChanged, а мысль
«уход из окна не выбрасывает страницу» шла трижды — в доке класса, в доке
свойства page и во встроенном комментарии в start(). Осталось два абзаца: чем
провайдер является и что после stop() он обязан молчать — это межтиповой
инвариант, из одного файла его не видно.

Убраны и три дока, ушедшие из своей ответственности или пересказывавшие подпись:
contentView рассказывал, как контейнер растягивает вью; второй абзац reload() —
про плейсхолдер и события хосту; isShown повторял собственное имя.

Встроенные комментарии не тронуты: каждый объясняет отсутствующую строку,
развилку или внешнюю причину — то, чего в коде не прочитать.

* MOBILE-323 PR Fix

* MOBILE-323 Remove unused tests

---------

Co-authored-by: Vailence <utekeshev@mindbox.cloud>
Vailence added a commit that referenced this pull request Aug 12, 2026
…, actions (#751)

* MOBILE-323: Add the embeddedBlocks log category

Своя категория логов у встроенных блоков: их путь — резолв id, страница,
бюджет ожидания — читается в логах отдельно от инаппов.

Счётчик категорий в LogPrimitivesTests обновлён здесь же: это не новый тест,
а утверждение, которое обязано ехать вместе с самим enum.

* MOBILE-323: Add the embedded block web contract

Словарь, на котором страница блока разговаривает с нативной стороной, и типы,
которыми говорят между собой все остальные слои.

Ядро разбирает только core-слой — ready, heightChanged и empty, они нужны любому
блоку. Всё остальное с валидным конвертом уходит механике как action: ядро не
знает и не должно знать словарь конкретной механики.

Состояния контейнера намеренно internal: хост узнаёт только исход — показан блок
или нет, — поэтому менять сам путь к исходу можно без изменения публичного API.

Папка Mindbox/EmbeddedBlocks заведена синхронизируемой группой, поэтому файлы в
ней дальше подхватываются без правок проекта.

* MOBILE-323: Add the embedded block content resolver

Отвечает на единственный вопрос: что показывает блок с данным id.

Резолвер — общая точка всех контейнеров: несколько блоков с одним id
разрешаются одними данными, за которыми ходим один раз. Кэш на id и очередь
ожидающих — это и есть «одна загрузка на id»: второй блок встаёт в очередь, а не
идёт за данными сам. forceRefresh нужен перезагрузке блока: переехавший или
выключенный блок иначе вечно доставал бы из кэша прежний адрес.

Пока конфига из админки нет, любой id разрешается в статическую страницу ленты
сторизов. Это единственное место, которое заменит настоящий конфиг: кэш и
очередь при этом не изменятся.

Рядом — подмена содержимого по id: она встаёт ровно на место будущего конфига и
сильнее кэша, потому что приёмка переключает сценарий на ходу.

* MOBILE-323: Add the embedded block page in WKWebView

Страница блока и шов, за которым живёт весь WebKit: перевод её сообщений в
состояния блока дальше проверяется без реального вебвью и без сети.

Вебвью берётся из InAppWebViewFactory — того же места, где настраиваются вебвью
инаппов, — поэтому блок получает тот же user agent, тот же WKWebsiteDataStore и
общий с инаппами HTTP-кеш.

Навигация судит исключительно о своём: загрузка не состоялась или документ
доехал. Готовность блока из этого не следует — о ней говорит сама страница своим
ready. Отменённая навигация при этом провалом не считается: WebKit отдаёт
NSURLErrorCancelled и когда навигацию вытеснил клиентский редирект, и когда её
остановил наш собственный cancel() на уехавшем с экрана блоке. Выдать это за
провал значит свернуть исправный блок насовсем.

Мост живёт столько же, сколько страница, и держится слабым прокси: WKUserContent-
Controller держит обработчик сильно, иначе страница и вебвью не освободятся.

Отладочная подмена готовности — для страниц, которые веб-контракт ещё не умеют.

* MOBILE-323: Add the embedded block page action router

Универсальный словарь действий страницы — один на все механики. Блок не знает,
какая механика внутри, поэтому любая страница, говорящая этим словарём, получает
нативное поведение без нового кода в SDK. Незнакомое действие — не ошибка:
словарь у веб-стороны может быть новее, чем у SDK.

openUrl открывает не что попало. Страница блока приезжает из сети, поэтому решать
за пользователя, что откроет система, ей не положено: tel:, sms:, itms-apps: и
схемы чужих приложений — это уже не переход по контенту, а действие от его имени,
и canOpenURL для них проходит. Разрешено то, что никуда пользователя не увозит:
веб-адреса и диплинки в само это приложение из CFBundleURLTypes.

Открытие вынесено за шов EmbeddedBlockURLOpening — и чтобы политику можно было
проверить тестами, и на будущее: в SDK открытие ссылок уже живёт в
MindboxURLHandlerDelegate, и когда блоки поедут на общий мост инаппов, здесь
окажется он, а не UIApplication напрямую.

* MOBILE-323: Add the debug content override SPI

Отладочное управление содержимым блоков — для тестового приложения и приёмки.

Подменяет ответ на вопрос «что стоит за этим id», то есть встаёт ровно на место
конфига из админки. Всё ниже — резолвер, страница, а дальше провайдер и бюджет
ожидания у контейнера — работает без изменений, поэтому приёмка проверяет боевой
путь, а не отдельный тестовый режим. Разметкой задаются сценарии, которых в сети
нет: страница, сообщающая «пусто», молчащая страница, страница с ответом после
таймаута.

Не часть публичного API: доступно только через @_spi(Internal) import Mindbox. Из
релизных сборок не вырезано намеренно — QA проверяет ровно то, что уходит
клиентам, — поэтому каждая установка подмены пишется в лог.

* MOBILE-323: Add test infrastructure for embedded blocks

Тег для сьютов блоков, папка MindboxTests/EmbeddedBlocks синхронизируемой группой
и заготовка моков.

В моках пока только то, что нужно тестам этой части: адрес страницы-заготовка и
открыватель ссылок, который ничего не открывает, — политику схем иначе не
проверить, canOpenURL в тестовом окружении пропускает системные схемы.

* MOBILE-323: Add tests for the embedded block content resolver

Резолвер проверяется главным своим обещанием: сколько блоков ни спросило бы про
один id, за данными идём один раз. Пока конфиг синхронный это незаметно, с сетью
это разница между одним запросом и N.

Рядом — кэш, обход кэша перезагрузкой и то, что отладочная подмена сильнее и
данных, и кэша: приёмка переключает сценарий на ходу.

* MOBILE-323: Add tests for the embedded block page

Страница судит только о своём, и проверяется именно это разделение: настоящая
ошибка сети — провал, а отменённая навигация — нет. Отмена при этом страницу не
глушит: следующая настоящая ошибка приходит как обычно.

* MOBILE-323: Add tests for the page action router

Обе ветки handle и вся политика схем: веб-адреса и диплинки хоста открываются,
tel:, sms:, itms-apps:, mailto: и схемы чужих приложений — нет, причём отказ не
отменяется тем, что система такую ссылку умеет открыть. Плюс кривые payload'ы,
незнакомое действие и разбор CFBundleURLTypes.

* MOBILE-323: Add the embedded block content provider

Переводит сообщения страницы в состояния блока. Не рисует контент и не знает
механик: спрашивает у резолвера, что стоит за id, разбирает core-слой, а действия
сверх него отдаёт универсальному обработчику.

Готовность определяет только сама страница: ready — показываем, empty —
показывать нечего. Навигация судит исключительно о своём, молчащая страница
готовой не становится — её добьёт бюджет ожидания у контейнера. Нулевая высота в
ready значит сломанную вёрстку: «показывать нечего» страница сообщает явным empty.

Исход попытки хранится явно, а не пачкой флагов. Он переживает stop(), потому что
это свойство страницы, а не факта нахождения в окне: уход блока с экрана не
выбрасывает уже отрендеренную страницу, и возврат показывает её снова, без сети и
без шиммера. При этом провал и empty страницу не убивают — она жива и может
продолжать говорить, — поэтому известный исход служит и признаком того, что блока
на экране больше нет: действия от невидимого блока не выполняются. За ним не
стоит ни одного касания пользователя, а openUrl увёл бы человека из приложения
на пустом месте.

Экземпляр принадлежит одному контейнеру: ничего общего между контейнерами здесь
нет, это и делает возможными несколько независимых блоков с одним id. Номер
попытки отсекает резолв, доехавший уже после остановки или перезагрузки.

Счётчик живых блоков на id — диагностика, а не механика: два блока с одним id
законны, но чаще это скопированный id или переиспользованная ячейка, а у обоих
случаев нет симптомов кроме «блок оказался не там, где ждали».

* MOBILE-323: Add the content provider factory

Собирает провайдер под конкретный блок: резолвер и обработчик действий общие на
все блоки, а провайдер — свой на каждый. Это и делает блоки с одинаковым id
независимыми друг от друга.

Потребителей у фабрики появится два, и оба в следующей части: DI-регистрация и
публичный init контейнера. Здесь она едет вместе с провайдером, потому что
описывает его модель владения, а не способ его достать.

* MOBILE-323: Add test doubles for the block content provider

Дописывает моки до среза этой части: страница без WebKit, фабрика страниц со
счётчиком созданных, резолвер с отложенным ответом, обработчик действий и общая
заготовка провайдера со всеми подменёнными зависимостями.

Резолвер умеет держать ответ до отдельной команды: так проверяется резолв,
доехавший уже после остановки или перезагрузки блока. Фабрика считает страницы,
потому что перезагрузка обязана создать новую, а возврат блока в окно — нет.

* MOBILE-323: Add tests for the embedded block content provider

Весь путь блока без WebKit и без сети: резолв, показ по ready, пустой блок,
сломанная нулевая высота, провал загрузки, действия страницы, отладочная подмена
готовности, остановка и перезапуск, перезагрузка.

Отдельно закреплено то, на что опираются соседние слои: после stop() провайдер
молчит целиком — иначе контейнер не смог бы свернуть просроченный блок; уже
отрендеренная страница на возврате в окно показывается как есть, без сети и
шиммера, а блок, который показать не удалось, получает новую попытку; выброшенная
перезагрузкой страница не может доложить в новую попытку ни сообщением, ни
провалом, ни через подмену готовности.

Действия проверяются с обеих сторон: из показанного блока доходят до обработчика,
из схлопнутого — нет, ни после empty, ни после провала, ни после нулевой высоты, —
а новая попытка снова их принимает.

Счётчику живых блоков в каждом тесте свой id: он общий на процесс, иначе тесты,
идущие параллельно, считали бы блоки друг друга.

* MOBILE-323: Add tests for the content provider factory

Фабрика приехала без тестов, а её обещание — то, на чём держится независимость
блоков с одинаковым id: провайдер свой на каждый блок, резолвер общий на все.
Проверяется и то и другое, плюс что провайдер собран под запрошенный id.

Резолвер в тестах отвечает «пусто»: страницу для такого блока не создают, поэтому
настоящий вебвью фабрике здесь не нужен.

* MOBILE-323: Trim duplicated comments in the content provider

Док класса пересказывал то, что уже сказано ниже по файлу: правила готовности —
у apply(height:) и handleLoadFinish, владение высотой — у heightChanged, а мысль
«уход из окна не выбрасывает страницу» шла трижды — в доке класса, в доке
свойства page и во встроенном комментарии в start(). Осталось два абзаца: чем
провайдер является и что после stop() он обязан молчать — это межтиповой
инвариант, из одного файла его не видно.

Убраны и три дока, ушедшие из своей ответственности или пересказывавшие подпись:
contentView рассказывал, как контейнер растягивает вью; второй абзац reload() —
про плейсхолдер и события хосту; isShown повторял собственное имя.

Встроенные комментарии не тронуты: каждый объясняет отсутствующую строку,
развилку или внешнюю причину — то, чего в коде не прочитать.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* MOBILE-323: Add the block layer host

Держит в контейнере ровно одну вью, растянутую по его краям. Слои блока —
плейсхолдер, контент, экран ошибки — взаимоисключающие: показать новый значит
снять прежний.

Показанная вью запоминается отдельно от свойств контейнера, потому что подменить
её хост может в любой момент, а снимать надо ту, что действительно висит, а не ту,
что лежит в свойстве сейчас.

* MOBILE-323: Add the block ready timeout

Бюджет на то, чтобы показаться, принадлежит контейнеру, а не контенту: чем бы блок
ни оказался внутри, вёрстка хоста не ждёт его вечно.

Считается время ожидания пользователя, а не календарное: пока блока никто не
ждёт — приложение в фоне, контейнер вне окна — отсчёт стоит. Но именно стоит, а не
начинается заново: потраченное запоминается, и попытка продолжает бюджет с того
места, где её прервали. Пауза, отдающая полный бюджет заново, не заканчивается
никогда — пользователь, переключающийся между приложениями каждые пять секунд,
продлевал бы ожидание блока бесконечно, и вёрстка ждала бы его вечно. Ровно то,
против чего бюджет и заведён. Полный бюджет получает только новая попытка.

Часы вынесены отдельным швом: считать потраченное без них нельзя, а тесты не могут
ждать бюджет целиком — им нужно уметь сказать, что время прошло.

Загрузку пауза не трогает: она идёт своим чередом, в фоне её тормозит система, а
не SDK.

* MOBILE-323: Add the default loading shimmer

Плейсхолдер, который блок показывает, пока грузится, если хост не дал своего.
Занимает весь контейнер: место под блок должно быть занято сразу, а занятое место
не должно выглядеть пустым.

* MOBILE-323: Add the UIKit embedded block container

Публичный API: контейнер создаётся с id блока из админки и высотой, которую блок
должен занять. Хост ставит его куда угодно и задаёт только положение и ширину —
высоту контейнер заявляет сам через intrinsicContentSize: данную при создании,
пока контент грузится и показан, и 0, когда показывать нечего. Оба исхода
настраиваются: placeholderView заменяет штатный шиммер, errorView — это согласие
показать провал вместо схлопывания.

Жизненным циклом владеет SDK: контент стартует, когда блок попадает в окно, и
останавливается, когда уходит. Публичного способа запустить его руками нет.

Внутри — машина из четырёх состояний контента и одного видимого слоя на каждое.
Три решения, которые стоят за ней:

Место, единожды отданное хосту, назад не забирается. Блок, который показать не
удалось, на возврате в окно пробует снова, но контейнер под эту попытку места уже
не занимает и шиммером не мигает — иначе он дёргал бы вёрстку на свою высоту и
мигал на каждый свой проход по экрану, ничего в итоге не показывая. Разворачивает
блок только показанный контент или явная перезагрузка.

Тот же делегат — не новый подписчик. Хост штатно переприсваивает его на каждой
переиспользованной ячейке, и отдавать ему на это уже услышанный исход нельзя: на
исход он перестраивает вёрстку, а перестройка вёрстки снова переприсваивает
делегата.

Исходы отдаются на следующем витке главной очереди: состояние может измениться
посреди прохода layout, и заходить оттуда в код хоста — верный способ сломать его
вёрстку.

EmbeddedBlockPresentation — снимок показа для SwiftUI-обёртки: она сама назначает
себе высоту и сама рисует слои хоста, потому что вью, отданная контейнеру через
отдельный UIHostingController, выпадает из дерева SwiftUI и теряет его окружение.

* MOBILE-323: Register embedded blocks in DI

Резолвер общий: его кэш на id и очередь ожидающих — это и есть «одна загрузка
данных на id» для всех контейнеров сразу. Обработчик действий общий потому, что не
имеет состояния. Провайдеры, наоборот, фабрика делает на каждый блок свой — так
блоки остаются независимыми друг от друга.

Файл прописан в проект вручную: Mindbox/DI/Injections — обычная группа, а не
синхронизируемая, в отличие от папок самих блоков.

* MOBILE-323: Add tests for the block layer host

Хост слоёв приехал без прямого покрытия: контейнер задевает его косвенно, но
собственное обещание — ровно одна вью, растянутая по краям, — проверить надо
отдельно.

Кроме подмены и снятия закреплены две тонкости: повторный показ той же вью
констрейнты не пересобирает (контейнер зовёт show на каждую смену состояния), а
вью, снятую снаружи, показ обязан вернуть на место.

* MOBILE-323: Add tests for the block ready timeout

Сколько бюджета «уже потрачено», тесты задают подменёнными часами, а ждут только
тот огрызок, который остался. Иначе проверка «продолжается остаток, а не выдаётся
полный бюджет» сводилась бы к измерению задержек секундомером.

Главный тест — пять пауз по четверти бюджета не растягивают его сверх срока: это
ровно тот сценарий, из-за которого пауза со сбросом непригодна.

Уход в фон и возврат здесь не проверяются: это глобальные нотификации, они долетят
до блоков из тестов, идущих рядом. Провод от них к паузе проверяет контейнер, у
которого блок один.

* MOBILE-323: Add tests for the UIKit embedded block container

Высота, слои, события хосту и жизненный цикл по окну — всё через настоящий
провайдер: единственный шов внутри блока это страница, и подменять больше нечего.

Закреплены три решения, из-за которых контейнер и выглядит так:

Блок, который показать не удалось, на возврате в окно пробует снова, но остаётся
свёрнутым и шиммером не мигает; разворачивает его только показанный контент или
явная перезагрузка. Пустой блок ведёт себя так же.

Тот же делегат, присвоенный повторно, уже услышанный исход не получает — иначе
хост, перестраивающий вёрстку в onFail, укатился бы в цикл на скролле. А другой
делегат исход обязан услышать, даже если тот случился до подписки.

Бюджет ожидания в фоне встаёт и на возврате продолжается: пользователь не должен
возвращаться в приложение к блоку, который сдался, ни разу не побывав на экране.

Моки дописаны последним нужным дублёром — делегатом со списком услышанных событий.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* MOBILE-323 Less comments

* MOBILE-323: Translate embedded block comments to English

Also carries the pending working-tree changes: the URL opener is dropped
from the action router until the in-app web view route logic is reused,
resolver answers are delivered on the main thread, and the page no longer
detaches its bridge in deinit.

* MOBILE-323: Fix the broken applyColors signature in the shimmer

A stray text fragment had been committed into the declaration line of
applyColors(), leaving the Mindbox target uncompilable since de08d91.
Every commit after it, tests included, was stacked on code that never
built.

Restores the signature and keeps the deinit that the same change added.

* MOBILE-323: Split the nothing-to-show branch out of the layer host

Showing nothing was falling through the same guard as showing a view,
where the superview comparison against a nil view is always true and the
condition therefore read as the opposite of what it meant.

Behaviour is unchanged; adds the missing test for showing nothing on a
host that already shows nothing.

* MOBILE-323: Make the ready timeout testable and report a zero height

The timeout kept its notification centre and its scheduler hardwired, so
the pause in the background was untestable and every budget test had to
wait real time. Both become injectable, as the clock already was, and the
container takes the whole budget instead of its duration.

Drops all 20 sleeps from the embedded block tests, which now declare that
time is up and assert the delay a countdown was armed with. Adds the
background and foreground cases the consumed accounting exists for.

A block created with a height of zero or less also reserves no space and
stays invisible while still reporting its outcome, which is the likeliest
integration mistake and now goes to the log as an error.

* Update Mindbox/EmbeddedBlocks/Resolver/EmbeddedBlockResolver.swift

Co-authored-by: Sergei Semko <28645140+justSmK@users.noreply.github.com>

* MOBILE-323 PR Fix

* MOBILE-323: Embedded block content provider (#754)

* MOBILE-323: Add the embedded block content provider

Переводит сообщения страницы в состояния блока. Не рисует контент и не знает
механик: спрашивает у резолвера, что стоит за id, разбирает core-слой, а действия
сверх него отдаёт универсальному обработчику.

Готовность определяет только сама страница: ready — показываем, empty —
показывать нечего. Навигация судит исключительно о своём, молчащая страница
готовой не становится — её добьёт бюджет ожидания у контейнера. Нулевая высота в
ready значит сломанную вёрстку: «показывать нечего» страница сообщает явным empty.

Исход попытки хранится явно, а не пачкой флагов. Он переживает stop(), потому что
это свойство страницы, а не факта нахождения в окне: уход блока с экрана не
выбрасывает уже отрендеренную страницу, и возврат показывает её снова, без сети и
без шиммера. При этом провал и empty страницу не убивают — она жива и может
продолжать говорить, — поэтому известный исход служит и признаком того, что блока
на экране больше нет: действия от невидимого блока не выполняются. За ним не
стоит ни одного касания пользователя, а openUrl увёл бы человека из приложения
на пустом месте.

Экземпляр принадлежит одному контейнеру: ничего общего между контейнерами здесь
нет, это и делает возможными несколько независимых блоков с одним id. Номер
попытки отсекает резолв, доехавший уже после остановки или перезагрузки.

Счётчик живых блоков на id — диагностика, а не механика: два блока с одним id
законны, но чаще это скопированный id или переиспользованная ячейка, а у обоих
случаев нет симптомов кроме «блок оказался не там, где ждали».

* MOBILE-323: Add the content provider factory

Собирает провайдер под конкретный блок: резолвер и обработчик действий общие на
все блоки, а провайдер — свой на каждый. Это и делает блоки с одинаковым id
независимыми друг от друга.

Потребителей у фабрики появится два, и оба в следующей части: DI-регистрация и
публичный init контейнера. Здесь она едет вместе с провайдером, потому что
описывает его модель владения, а не способ его достать.

* MOBILE-323: Add test doubles for the block content provider

Дописывает моки до среза этой части: страница без WebKit, фабрика страниц со
счётчиком созданных, резолвер с отложенным ответом, обработчик действий и общая
заготовка провайдера со всеми подменёнными зависимостями.

Резолвер умеет держать ответ до отдельной команды: так проверяется резолв,
доехавший уже после остановки или перезагрузки блока. Фабрика считает страницы,
потому что перезагрузка обязана создать новую, а возврат блока в окно — нет.

* MOBILE-323: Add tests for the embedded block content provider

Весь путь блока без WebKit и без сети: резолв, показ по ready, пустой блок,
сломанная нулевая высота, провал загрузки, действия страницы, отладочная подмена
готовности, остановка и перезапуск, перезагрузка.

Отдельно закреплено то, на что опираются соседние слои: после stop() провайдер
молчит целиком — иначе контейнер не смог бы свернуть просроченный блок; уже
отрендеренная страница на возврате в окно показывается как есть, без сети и
шиммера, а блок, который показать не удалось, получает новую попытку; выброшенная
перезагрузкой страница не может доложить в новую попытку ни сообщением, ни
провалом, ни через подмену готовности.

Действия проверяются с обеих сторон: из показанного блока доходят до обработчика,
из схлопнутого — нет, ни после empty, ни после провала, ни после нулевой высоты, —
а новая попытка снова их принимает.

Счётчику живых блоков в каждом тесте свой id: он общий на процесс, иначе тесты,
идущие параллельно, считали бы блоки друг друга.

* MOBILE-323: Add tests for the content provider factory

Фабрика приехала без тестов, а её обещание — то, на чём держится независимость
блоков с одинаковым id: провайдер свой на каждый блок, резолвер общий на все.
Проверяется и то и другое, плюс что провайдер собран под запрошенный id.

Резолвер в тестах отвечает «пусто»: страницу для такого блока не создают, поэтому
настоящий вебвью фабрике здесь не нужен.

* MOBILE-323: Trim duplicated comments in the content provider

Док класса пересказывал то, что уже сказано ниже по файлу: правила готовности —
у apply(height:) и handleLoadFinish, владение высотой — у heightChanged, а мысль
«уход из окна не выбрасывает страницу» шла трижды — в доке класса, в доке
свойства page и во встроенном комментарии в start(). Осталось два абзаца: чем
провайдер является и что после stop() он обязан молчать — это межтиповой
инвариант, из одного файла его не видно.

Убраны и три дока, ушедшие из своей ответственности или пересказывавшие подпись:
contentView рассказывал, как контейнер растягивает вью; второй абзац reload() —
про плейсхолдер и события хосту; isShown повторял собственное имя.

Встроенные комментарии не тронуты: каждый объясняет отсутствующую строку,
развилку или внешнюю причину — то, чего в коде не прочитать.

* MOBILE-323 PR Fix

* MOBILE-323 Remove unused tests

---------

Co-authored-by: Vailence <utekeshev@mindbox.cloud>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* MOBILE-323 Less comments

* MOBILE-323: Translate embedded block comments to English

Also carries the pending working-tree changes: the URL opener is dropped
from the action router until the in-app web view route logic is reused,
resolver answers are delivered on the main thread, and the page no longer
detaches its bridge in deinit.

* Update Mindbox/EmbeddedBlocks/Resolver/EmbeddedBlockResolver.swift

Co-authored-by: Sergei Semko <28645140+justSmK@users.noreply.github.com>

* MOBILE-323 PR Fix

* MOBILE-323: Embedded block content provider (#754)

* MOBILE-323: Add the embedded block content provider

Переводит сообщения страницы в состояния блока. Не рисует контент и не знает
механик: спрашивает у резолвера, что стоит за id, разбирает core-слой, а действия
сверх него отдаёт универсальному обработчику.

Готовность определяет только сама страница: ready — показываем, empty —
показывать нечего. Навигация судит исключительно о своём, молчащая страница
готовой не становится — её добьёт бюджет ожидания у контейнера. Нулевая высота в
ready значит сломанную вёрстку: «показывать нечего» страница сообщает явным empty.

Исход попытки хранится явно, а не пачкой флагов. Он переживает stop(), потому что
это свойство страницы, а не факта нахождения в окне: уход блока с экрана не
выбрасывает уже отрендеренную страницу, и возврат показывает её снова, без сети и
без шиммера. При этом провал и empty страницу не убивают — она жива и может
продолжать говорить, — поэтому известный исход служит и признаком того, что блока
на экране больше нет: действия от невидимого блока не выполняются. За ним не
стоит ни одного касания пользователя, а openUrl увёл бы человека из приложения
на пустом месте.

Экземпляр принадлежит одному контейнеру: ничего общего между контейнерами здесь
нет, это и делает возможными несколько независимых блоков с одним id. Номер
попытки отсекает резолв, доехавший уже после остановки или перезагрузки.

Счётчик живых блоков на id — диагностика, а не механика: два блока с одним id
законны, но чаще это скопированный id или переиспользованная ячейка, а у обоих
случаев нет симптомов кроме «блок оказался не там, где ждали».

* MOBILE-323: Add the content provider factory

Собирает провайдер под конкретный блок: резолвер и обработчик действий общие на
все блоки, а провайдер — свой на каждый. Это и делает блоки с одинаковым id
независимыми друг от друга.

Потребителей у фабрики появится два, и оба в следующей части: DI-регистрация и
публичный init контейнера. Здесь она едет вместе с провайдером, потому что
описывает его модель владения, а не способ его достать.

* MOBILE-323: Add test doubles for the block content provider

Дописывает моки до среза этой части: страница без WebKit, фабрика страниц со
счётчиком созданных, резолвер с отложенным ответом, обработчик действий и общая
заготовка провайдера со всеми подменёнными зависимостями.

Резолвер умеет держать ответ до отдельной команды: так проверяется резолв,
доехавший уже после остановки или перезагрузки блока. Фабрика считает страницы,
потому что перезагрузка обязана создать новую, а возврат блока в окно — нет.

* MOBILE-323: Add tests for the embedded block content provider

Весь путь блока без WebKit и без сети: резолв, показ по ready, пустой блок,
сломанная нулевая высота, провал загрузки, действия страницы, отладочная подмена
готовности, остановка и перезапуск, перезагрузка.

Отдельно закреплено то, на что опираются соседние слои: после stop() провайдер
молчит целиком — иначе контейнер не смог бы свернуть просроченный блок; уже
отрендеренная страница на возврате в окно показывается как есть, без сети и
шиммера, а блок, который показать не удалось, получает новую попытку; выброшенная
перезагрузкой страница не может доложить в новую попытку ни сообщением, ни
провалом, ни через подмену готовности.

Действия проверяются с обеих сторон: из показанного блока доходят до обработчика,
из схлопнутого — нет, ни после empty, ни после провала, ни после нулевой высоты, —
а новая попытка снова их принимает.

Счётчику живых блоков в каждом тесте свой id: он общий на процесс, иначе тесты,
идущие параллельно, считали бы блоки друг друга.

* MOBILE-323: Add tests for the content provider factory

Фабрика приехала без тестов, а её обещание — то, на чём держится независимость
блоков с одинаковым id: провайдер свой на каждый блок, резолвер общий на все.
Проверяется и то и другое, плюс что провайдер собран под запрошенный id.

Резолвер в тестах отвечает «пусто»: страницу для такого блока не создают, поэтому
настоящий вебвью фабрике здесь не нужен.

* MOBILE-323: Trim duplicated comments in the content provider

Док класса пересказывал то, что уже сказано ниже по файлу: правила готовности —
у apply(height:) и handleLoadFinish, владение высотой — у heightChanged, а мысль
«уход из окна не выбрасывает страницу» шла трижды — в доке класса, в доке
свойства page и во встроенном комментарии в start(). Осталось два абзаца: чем
провайдер является и что после stop() он обязан молчать — это межтиповой
инвариант, из одного файла его не видно.

Убраны и три дока, ушедшие из своей ответственности или пересказывавшие подпись:
contentView рассказывал, как контейнер растягивает вью; второй абзац reload() —
про плейсхолдер и события хосту; isShown повторял собственное имя.

Встроенные комментарии не тронуты: каждый объясняет отсутствующую строку,
развилку или внешнюю причину — то, чего в коде не прочитать.

* MOBILE-323 PR Fix

* MOBILE-323 Remove unused tests

---------

Co-authored-by: Vailence <utekeshev@mindbox.cloud>

* MOBILE-323 Unused tests removed

* MOBILE-323: Add the SwiftUI embedded block (#756)

* MOBILE-323: Add the SwiftUI embedded block

Тот же блок в SwiftUI: создаётся с id и высотой, ставится куда угодно, а
плейсхолдер и экран ошибки задаются модификаторами на самом блоке.

Слои хоста рисует обёртка, а не контейнер. Вью, отданная контейнеру через
отдельный UIHostingController, не входит в дерево SwiftUI и не видит его
окружения: плейсхолдер с @EnvironmentObject просто падает, а заданные хостом
шрифт, цвет и локаль до него не доезжают. Поэтому контейнер получает под каждый
заявленный слой прозрачную заглушку — держать место, — а красит это место SwiftUI
поверх. Отсюда и снимок показа: обёртке нужен не только размер, но и текущий слой.

И id, и высоту контейнер получает при создании и потом не меняет, поэтому другое
значение любого из них — это другой блок, который надо собрать заново. Без явной
идентичности хост, подставивший другой id, продолжал бы видеть содержимое прежнего:
SwiftUI переиспользовал бы уже созданный контейнер.

Координатор переставляется на свежие замыкания на каждом проходе body, а заглушки
ставятся и снимаются на каждом обновлении: модификатор мог быть применён по
условию, поэтому слой может появиться после первого прохода — и точно так же
исчезнуть.

* MOBILE-323: Add tests for the SwiftUI embedded block

Модификаторы, идентичность блока и мост между контейнером и обёрткой: заглушки под
заявленные слои ставятся и снимаются по состоянию модификаторов, а показ и исход
доезжают до обёртки.

Идентичность проверяется отдельно, потому что на ней держится главное: другой id
или другая высота — это другой блок, а не обновление текущего.

---------

Co-authored-by: Vailence <utekeshev@mindbox.cloud>

* MOBILE-323 Remove Identity

* MOBILE-323 isDetached

* MOBILE-323: Translate embedded block comments and restore test coverage

Translate all remaining Russian comments in EmbeddedBlocks sources and
tests to English. Fix the ready timeout's clock to be monotonic so an
NTP correction or manual clock change cannot shrink or negate spent
budget. Restore the previously deleted container, provider, factory
and SwiftUI wrapper test suites, adapted to the current identity and
timeout seams.

* Update Mindbox/EmbeddedBlocks/Public/MindboxEmbeddedBlock/MindboxEmbeddedBlock.swift

Co-authored-by: Sergei Semko <28645140+justSmK@users.noreply.github.com>

* Update Mindbox/EmbeddedBlocks/Public/MindboxEmbeddedBlock/MindboxEmbeddedBlock.swift

Co-authored-by: Sergei Semko <28645140+justSmK@users.noreply.github.com>

---------

Co-authored-by: Vailence <utekeshev@mindbox.cloud>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sergei Semko <28645140+justSmK@users.noreply.github.com>
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.

3 participants