fix: close the review findings on the issue wave

Two reviewers over the #1345/#1348/#1349 wave; no blockers, two
majors, several minors — all fixed.

- La16 was still billed at 4 B/px: the no-conversion special case
  keyed on native_bpp == 4, which La16 also is — but only Rgba8 is a
  real move in into_rgba8, so La16's decode peak is 8. The case now
  keys on the color type, and a table test pins every layout's peak
  (plus GIF's screen+frame 8) so the accounting can't silently drift.
- A control-routed open of a damaged-but-recoverable drawing parked
  the operation behind the RecoveryPrompt modal (hang until the JS
  timeout, silent drop if another op arrived, or false success when a
  human declined). The open op now settles as failed with
  recovery_required and the parse error; the prompt stays for humans,
  automation can retry through the recovery flow.
- json_values_match: integers beyond f64 precision compared by value
  (a u64 could round-trip-match a float); integral pairs now compare
  exactly and mixed integer/float matches only when the float is whole
  and equals the integer.
- A queued web open settles with the explicit queued marker whenever
  the open slot was busy (the old name check made same-named in-flight
  opens look completed with an empty result).
- ocs-control.js resolves  with the API object reference, not
  window.ocsControl (undefined if the bridge already existed when the
  script ran); DATALINK on web says the typed path is superseded by
  the picker instead of silently discarding it; the wipeout depth test
  probes past the page background's rectangle; and the plot-path doc
  comment names the function it actually mirrors.
This commit is contained in:
Afonsop committed 2026-09-18 14:26:20 +01:00
1 parent 2c38de53e2
commit d719fb7778
30 files changed
+143 -18

No files matched your search

+1
View File
@@ -3591,6 +3591,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = يجري فتح مناقشات OCS للمساعدة والأسئلة...
.opening-ocs-web = يجري فتح OCS Web...
.opening-the-command-reference = فتح مرجع الأوامر...
.browser-cannot-open-typed-path = لا يستطيع المتصفح فتح مسار مكتوب؛ اختر الملف بدلاً من ذلك.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" محمّل بالفعل بمحتوى مختلف؛ تم الاحتفاظ بالملف الموجود.
.opening-patreon-page = يجري فتح صفحة Patreon...
.opening-feedback-page = يجري فتح صفحة الملاحظات...
+1
View File
@@ -3578,6 +3578,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Отваряне на OCS дискусии за помощ и въпроси...
.opening-ocs-web = Отваряне на OCS уебсайта...
.opening-the-command-reference = Отваряне на справочника за команди...
.browser-cannot-open-typed-path = Браузърът не може да отвори въведен път; изберете файла.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" вече е зареден с различно съдържание; съществуващият файл е запазен.
.opening-patreon-page = Отваряне на страницата в Patreon...
.opening-feedback-page = Отваряне на страницата за обратна връзка...
+1
View File
@@ -3576,6 +3576,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Otevírání diskuzí OCS pro nápovědu a dotazy...
.opening-ocs-web = Otevírání webu OCS...
.opening-the-command-reference = Otevírání referenční příručky příkazů...
.browser-cannot-open-typed-path = Prohlížeč nemůže otevřít zadanou cestu; vyberte soubor.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" je již načten s jiným obsahem; existující soubor byl zachován.
.opening-patreon-page = Otevírání stránky Patreon...
.opening-feedback-page = Otevírá se stránka zpětné vazby...
+1
View File
@@ -3577,6 +3577,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Eröffnung OCS Diskussionen für Hilfe und Fragen...
.opening-ocs-web = Öffnen OCS Web...
.opening-the-command-reference = Öffnen der Befehlsreferenz...
.browser-cannot-open-typed-path = Der Browser kann einen eingegebenen Pfad nicht öffnen; wähle die Datei.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" ist bereits mit anderem Inhalt geladen; die vorhandene Datei wurde behalten.
.opening-patreon-page = Eröffnungsseite Patreon...
.opening-feedback-page = Feedbackseite öffnen...
+1
View File
@@ -3573,6 +3573,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Άνοιγμα των συζητήσεων OCS για βοήθεια και ερωτήσεις...
.opening-ocs-web = Άνοιγμα του OCS Web...
.opening-the-command-reference = Άνοιγμα της αναφοράς εντολών...
.browser-cannot-open-typed-path = Ο browser δεν μπορεί να ανοίξει πληκτρολογημένη διαδρομή· διάλεξε το αρχείο.
.already-loaded-different-contents-kept = Το "__ocs_fmt_0__" είναι ήδη φορτωμένο με διαφορετικό περιεχόμενο· διατηρήθηκε το υπάρχον αρχείο.
.opening-patreon-page = Άνοιγμα της σελίδας Patreon...
.opening-feedback-page = Άνοιγμα της σελίδας σχολίων...
+1
View File
@@ -3574,6 +3574,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Opening OCS Discussions for help and questions...
.opening-ocs-web = Opening OCS Web...
.opening-the-command-reference = Opening the command reference...
.browser-cannot-open-typed-path = The browser cannot open a typed path; pick the file instead.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" is already loaded with different contents; the existing file was kept.
.opening-patreon-page = Opening Patreon page...
.opening-feedback-page = Opening feedback page...
+1
View File
@@ -3579,6 +3579,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Abriendo Debates de OCS para obtener ayuda y hacer preguntas...
.opening-ocs-web = Abriendo OCS Web...
.opening-the-command-reference = Abriendo la referencia de comandos...
.browser-cannot-open-typed-path = El navegador no puede abrir una ruta escrita; elige el archivo.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" ya está cargado con contenido diferente; se conservó el archivo existente.
.opening-patreon-page = Abriendo la página de Patreon...
.opening-feedback-page = Abriendo la página de comentarios...
+1
View File
@@ -3578,6 +3578,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = OCS-keskustelujen avaaminen apua ja kysymyksiä varten...
.opening-ocs-web = Avataan OCS Web...
.opening-the-command-reference = Avataan komentoviittausta...
.browser-cannot-open-typed-path = Selain ei voi avata kirjoitettua polkua; valitse tiedosto.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" on jo ladattu eri sisällöllä; olemassa oleva tiedosto säilytettiin.
.opening-patreon-page = Avataan Patreon-sivua...
.opening-feedback-page = Palautesivua avataan...
+1
View File
@@ -3579,6 +3579,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Ouverture OCS Discussions pour l'aide et les questions...
.opening-ocs-web = Ouverture du site Web OCS...
.opening-the-command-reference = Ouverture de la référence des commandes...
.browser-cannot-open-typed-path = Le navigateur ne peut pas ouvrir un chemin saisi ; choisissez le fichier.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" est déjà chargé avec un contenu différent ; le fichier existant a été conservé.
.opening-patreon-page = Ouverture de la page Patreon...
.opening-feedback-page = Ouverture de la page de rétroaction...
+1
View File
@@ -3567,6 +3567,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = OCS मदद और प्रश्नों के लिए चर्चा खोलना।
.opening-ocs-web = OCS वेब खोलना ...
.opening-the-command-reference = कमांड संदर्भ खोला जा रहा है...
.browser-cannot-open-typed-path = ब्राउज़र टाइप किए गए पथ को नहीं खोल सकता; इसके बजाय फ़ाइल चुनें।
.already-loaded-different-contents-kept = "__ocs_fmt_0__" अलग सामग्री के साथ पहले से लोड है; मौजूदा फ़ाइल रखी गई।
.opening-patreon-page = Patreon पृष्ठ खोला जा रहा है…
.opening-feedback-page = प्रतिक्रिया पृष्ठ खोलना
+1
View File
@@ -3578,6 +3578,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = A OCS beszélgetések megnyitása segítségért és kérdésekért...
.opening-ocs-web = A OCS web megnyitása...
.opening-the-command-reference = A parancsreferencia megnyitása...
.browser-cannot-open-typed-path = A böngésző nem nyithat meg beírt útvonalat; válaszd a fájlt.
.already-loaded-different-contents-kept = A(z) "__ocs_fmt_0__" már betöltve eltérő tartalommal; a meglévő fájl megőrizve.
.opening-patreon-page = Patreon oldal megnyitása...
.opening-feedback-page = Visszajelzési oldal megnyitása...
+1
View File
@@ -3578,6 +3578,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Apertura delle discussioni OCS per assistenza e domande...
.opening-ocs-web = Apertura del Web OCS...
.opening-the-command-reference = Apertura del riferimento dei comandi...
.browser-cannot-open-typed-path = Il browser non può aprire un percorso digitato; scegli il file.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" è già caricato con contenuti diversi; il file esistente è stato mantenuto.
.opening-patreon-page = Apertura della pagina Patreon...
.opening-feedback-page = Apertura della pagina di feedback...
+1
View File
@@ -3567,6 +3567,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = ヘルプと質問のため OCS ディスカッションを開いています...
.opening-ocs-web = OCS Web を開いています...
.opening-the-command-reference = コマンドリファレンスを開いています...
.browser-cannot-open-typed-path = ブラウザは入力されたパスを開けません。ファイルを選択してください。
.already-loaded-different-contents-kept = "__ocs_fmt_0__" は既に別の内容で読み込まれています。既存のファイルを保持しました。
.opening-patreon-page = Patreon ページを開いています...
.opening-feedback-page = フィードバックページを開いています...
+1
View File
@@ -3578,6 +3578,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = OCS 열기 도움말 및 질문에 대한 토론...
.opening-ocs-web = OCS 웹을 여는 중...
.opening-the-command-reference = 명령 참조 열기...
.browser-cannot-open-typed-path = 브라우저는 입력한 경로를 열 수 없습니다. 파일을 선택하세요.
.already-loaded-different-contents-kept = "__ocs_fmt_0__"이(가) 이미 다른 내용으로 로드되었습니다. 기존 파일을 유지했습니다.
.opening-patreon-page = Patreon 페이지를 여는 중...
.opening-feedback-page = 피드백 페이지를 여는 중...
+1
View File
@@ -3577,6 +3577,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Openen OCS Discussie voor hulp en vragen...
.opening-ocs-web = Openen OCS Web...
.opening-the-command-reference = Commandoreferentie openen...
.browser-cannot-open-typed-path = De browser kan een getypt pad niet openen; kies het bestand.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" is al geladen met andere inhoud; het bestaande bestand is bewaard.
.opening-patreon-page = Patreon pagina openen...
.opening-feedback-page = Herinneringspagina openen...
+1
View File
@@ -3578,6 +3578,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Otwieranie dyskusji OCS w celu uzyskania pomocy i pytań...
.opening-ocs-web = Otwieranie OCS Web...
.opening-the-command-reference = Otwieranie dokumentacji poleceń...
.browser-cannot-open-typed-path = Przeglądarka nie otworzy wpisanej ścieżki; wybierz plik.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" jest już wczytany z inną zawartością; zachowano istniejący plik.
.opening-patreon-page = Otwieranie strony Patreon...
.opening-feedback-page = Otwieram stronę z opiniami...
+1
View File
@@ -3579,6 +3579,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Abrindo Discussões do OCS para ajuda e perguntas...
.opening-ocs-web = Abrindo OCS Web...
.opening-the-command-reference = Abrindo a referência de comandos...
.browser-cannot-open-typed-path = O navegador não pode abrir um caminho digitado; escolha o ficheiro.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" já está carregado com conteúdo diferente; o ficheiro existente foi mantido.
.opening-patreon-page = Abrindo página do Patreon...
.opening-feedback-page = Abrindo página de comentários...
+1
View File
@@ -3580,6 +3580,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Открытие OCS Дискуссии о помощи и вопросах
.opening-ocs-web = Открытие OCS Веб-сайт
.opening-the-command-reference = Открытие справочника команд...
.browser-cannot-open-typed-path = Браузер не может открыть введённый путь; выберите файл.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" уже загружен с другим содержимым; существующий файл сохранён.
.opening-patreon-page = Открытие страницы Patreon...
.opening-feedback-page = Открываем страницу обратной связи...
+1
View File
@@ -3529,6 +3529,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = Yardım ve sorular için OCS Tartışmaları açılıyor...
.opening-ocs-web = OCS Web açılıyor...
.opening-the-command-reference = Komut başvurusu açılıyor...
.browser-cannot-open-typed-path = Tarayıcı yazılan bir yolu açamaz; dosyayı seçin.
.already-loaded-different-contents-kept = "__ocs_fmt_0__" zaten farklı içerikle yüklü; mevcut dosya tutuldu.
.opening-patreon-page = Patreon sayfası açılıyor...
.opening-feedback-page = Geri bildirim sayfası açılıyor...
+1
View File
@@ -3562,6 +3562,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = 开幕OCS 讨论帮助和问题...
.opening-ocs-web = 打开OCS网络...
.opening-the-command-reference = 正在打开命令参考...
.browser-cannot-open-typed-path = 浏览器无法打开键入的路径;请改为选择文件。
.already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同内容加载;保留了现有文件。
.opening-patreon-page = 打开帕特伦的页面...
.opening-feedback-page = 打开反馈页面...
+1
View File
@@ -3574,6 +3574,7 @@ view =
.opening-ocs-discussions-for-help-and-questions = 開幕OCS 討論幫助和問題...
.opening-ocs-web = 開啟OCS網路...
.opening-the-command-reference = 正在開啟命令參考...
.browser-cannot-open-typed-path = 瀏覽器無法開啟輸入的路徑;請改為選擇檔案。
.already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同內容載入;保留了現有檔案。
.opening-patreon-page = 開啟帕特倫的頁面...
.opening-feedback-page = 開啟反饋頁面...
+10 -1
View File
@@ -1601,7 +1601,16 @@ impl OpenCADStudio {
// then runs from `DataLinkCsvPicked`.
#[cfg(target_arch = "wasm32")]
{
let _ = path;
// Say the typed argument is superseded — a silent
// redirect looks like the input was lost.
if !path.trim().is_empty() {
self.command_line.push_info(
crate::t!(
"The browser cannot open a typed path; pick the file instead."
)
.as_ref(),
);
}
return Some(Task::perform(
crate::io::pick_datalink_csv_web(),
Message::DataLinkCsvPicked,
+7 -3
View File
@@ -755,9 +755,13 @@ impl OpenCADStudio {
// stays pending until the open settles instead of
// completing before the drawing exists.
let task = self.update(Message::PollWebControl);
if self.opening.as_ref().is_none_or(|opening| opening.name != name) {
// Another open holds the slot; the poll opens this
// entry once it frees, after this operation settles.
// Another open holds the slot; the poll opens this entry
// once it frees, after this operation settles. Report
// `queued` whenever the slot was busy at submit — the
// name check skipped same-named in-flight opens, making
// the op look completed with an empty result before the
// queued entry had opened.
if self.opening.is_some() {
self.set_control_result(json!({"queued": true, "name": name}));
}
return Ok(task);
+21 -5
View File
@@ -1111,11 +1111,27 @@ fn requested_handle(request: &Value) -> Result<acadrust::Handle, String> {
/// bools and null stay exact; objects and arrays recurse.
fn json_values_match(stored: &Value, expected: &Value) -> bool {
match (stored, expected) {
(Value::Number(stored), Value::Number(expected)) => match (stored.as_f64(), expected.as_f64()) {
(Some(stored), Some(expected)) => stored == expected,
// Out-of-range or arbitrary-precision numbers keep exact equality.
_ => stored == expected,
},
(Value::Number(stored), Value::Number(expected)) => {
// Integers compare exactly (a u64 beyond f64 precision must not
// round-trip-match a float); mixed integer/float matches only
// when the float is whole and equals the integer.
match (stored.as_u64(), expected.as_u64()) {
(Some(stored), Some(expected)) => stored == expected,
(Some(stored), None) => match expected.as_f64() {
Some(expected) => expected.fract() == 0.0 && expected >= 0.0 && stored as f64 == expected,
None => false,
},
(None, Some(expected)) => match stored.as_f64() {
Some(stored) => stored.fract() == 0.0 && stored >= 0.0 && expected as f64 == stored,
None => false,
},
_ => match (stored.as_i64(), expected.as_i64(), stored.as_f64(), expected.as_f64()) {
(Some(stored), Some(expected), _, _) => stored == expected,
(_, _, Some(stored), Some(expected)) => stored == expected,
_ => stored == expected,
},
}
}
(Value::Object(stored), Value::Object(expected)) => {
stored.len() == expected.len()
&& stored.iter().all(|(key, value)| {
+25 -1
View File
@@ -1059,9 +1059,33 @@ impl OpenCADStudio {
}
if e.recovery_available {
if let Some(opening) = self.opening.as_mut() {
opening.recovery_error = Some(e.message);
let recovery_message = e.message.clone();
opening.recovery_error = Some(recovery_message.clone());
opening.recovery_read_stats = e.read_stats;
self.active_modal = Some(super::ModalKind::RecoveryPrompt);
// A control-routed open (automation) must not park
// behind a modal nobody may answer: settle the
// operation as failed with the parse error. The
// prompt stays for a human; automation retries with
// the recovery op if it wants the content.
// A control-routed open (automation) must not park
// behind a modal nobody may answer: settle the
// operation as failed with the parse error. The
// prompt stays for a human; automation can retry
// through the recovery flow if it wants the content.
#[cfg(target_arch = "wasm32")]
{
let settled = serde_json::json!({
"ok": false,
"status": "failed",
"code": "recovery_required",
"error": format!(
"the drawing is damaged and needs a recovery decision: {}",
recovery_message
),
});
self.set_control_result(settled);
}
return Task::none();
}
}
+9 -2
View File
@@ -2597,11 +2597,18 @@ mod tests {
"tied wipeout must mask the wire: {black} !< {last_wipe}"
);
// Below the wire (DRAWORDER under): depth wins and the wire paints
// back on top of the mask.
// back on top of the mask. The page BACKGROUND is also a white
// rectangle: skip past its `re` operator so the probe cannot
// accidentally measure the background if a future serializer
// paints it.
let mut page = test_page(vec![line_wire("70040", 0.0, BLACK)]);
page.content.wipeouts = vec![wipeout(-0.5)];
let stream = render_stream(page);
let wipe = fill_op_pos(&stream, "1 1 1 rg", 0);
let background = stream
.windows(2)
.position(|w| w == b"re")
.expect("the page background rectangle serializes");
let wipe = fill_op_pos(&stream, "1 1 1 rg", background);
let black = stroke_op_pos(&stream, "0 0 0 RG", 0);
assert!(
wipe < black,
+1
View File
@@ -1887,6 +1887,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Opening OCS Discussions for help and questions..." => Some(("view", "opening-ocs-discussions-for-help-and-questions")),
"Opening OCS Web..." => Some(("view", "opening-ocs-web")),
"Opening the command reference..." => Some(("view", "opening-the-command-reference")),
"The browser cannot open a typed path; pick the file instead." => Some(("display", "browser-cannot-open-typed-path")),
"\"{name}\" could not be sized from its header and decoding it exceeded the {limit} MB image limit of this build — it cannot be attached." => Some(("file", "image-refusal-unknown-size")),
"\"{name}\" is already loaded with different contents; the existing file was kept." => Some(("view", "already-loaded-different-contents-kept")),
"Opening Patreon page..." => Some(("view", "opening-patreon-page")),
+41 -3
View File
@@ -665,9 +665,14 @@ fn native_footprint(format: Option<image::ImageFormat>, color: Option<image::Col
_ => 4,
});
// `DynamicImage::into_rgba8` only MOVES for Rgba8; every other layout
// allocates the RGBA destination while the native buffer is still
// alive, so both coexist (image 0.25 buffer.rs cast_in_color_space).
let peak_bpp = if native_bpp == 4 { 4 } else { native_bpp + 4 };
// — including the 4 B/px La16 — allocates the RGBA destination while
// the native buffer is still alive, so both coexist (image 0.25
// buffer.rs cast_in_color_space).
let peak_bpp = if matches!(color, Some(image::ColorType::Rgba8)) {
4
} else {
native_bpp + 4
};
// GIF decodes the frame into its own buffer while the screen-sized
// output buffer is live — two RGBA-sized allocations coexist.
let peak_bpp = match format {
@@ -1261,6 +1266,39 @@ pub use web_fetch::{
mod tests {
use super::*;
/// The decode peak per layout: into_rgba8 is a MOVE for Rgba8 only —
/// every other layout pays native + RGBA coexisting. GIF additionally
/// carries the screen-sized output beside a (clamped) frame buffer.
#[test]
fn native_footprint_peaks_match_the_coexistence_math() {
use image::ColorType;
let png = image::ImageFormat::Png;
let gif = image::ImageFormat::Gif;
let cases = [
(ColorType::L8, 5u64),
(ColorType::La8, 6),
(ColorType::Rgb8, 7),
(ColorType::Rgba8, 4),
(ColorType::L16, 6),
// 4 B/px but NOT a move — the La16 hole this test pins.
(ColorType::La16, 8),
(ColorType::Rgb16, 10),
(ColorType::Rgba16, 12),
(ColorType::Rgb32F, 16),
(ColorType::Rgba32F, 20),
];
for (color, expected) in cases {
assert_eq!(
native_footprint(Some(png), Some(color)).peak_bpp,
expected,
"{color:?}"
);
}
assert_eq!(native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp, 8);
assert_eq!(native_footprint(None, None).peak_bpp, 8);
}
/// Encode a small deterministic PNG entirely in memory — the same codec
/// `decode_from_memory` reads, so no fixture file is needed.
fn png_bytes(w: u32, h: u32, tint: u8) -> Vec<u8> {
+1 -1
View File
@@ -566,7 +566,7 @@ impl Scene {
// canvas path re-reads the label per frame but this plot path used to
// clone the stale value, so every top-level hatch exported at depth 0
// and DRAWORDER (SORTENTS) never reached the PDF (#1345). Refresh the
// per-entity label here exactly like `hatch_models_for_view`.
// per-entity label here exactly like `synced_hatch_models`.
let depths = self.draw_depth_map();
for (&handle, model) in self.hatches.iter() {
let Some(source) = self.document.get_entity(handle) else {
+7 -2
View File
@@ -21,7 +21,10 @@
let readyResolve;
const ready = new Promise(resolve => { readyResolve = resolve; });
const settle = () => {
readyResolve(window.ocsControl);
// apiObject is assigned to window.ocsControl at the END of this IIFE;
// resolve with the local reference so a synchronous bridge (script
// injected late) cannot settle `ready` with undefined.
readyResolve(apiObject);
window.dispatchEvent(new Event("ocs-ready"));
};
const poll = setInterval(() => {
@@ -68,5 +71,7 @@
return reply;
}
window.ocsControl = {ready, request};
const apiObject = {ready, request};
window.ocsControl = apiObject;
})();