kaloko 0.12.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/viewer/index.html CHANGED
@@ -902,6 +902,11 @@ details summary{cursor:pointer;font-size:12.5px;font-weight:500}
902
902
  /* a phone: the decisions sit in the bottom row, under the thumb (Approve on the right, wide), moving on above them */
903
903
  @media (max-width:560px){.rvm__foot--act{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-template-areas:"flash flash flash" "prev cmt next" "ret ok ok"}.rvm__foot--act .rvm__flash{grid-area:flash}.rvm__foot--act #rvPrev{grid-area:prev}.rvm__foot--act #rvCmt{grid-area:cmt}.rvm__foot--act #rvNext{grid-area:next}.rvm__foot--act #rvRet{grid-area:ret}.rvm__foot--act #rvOk{grid-area:ok}.rvm__foot--act #rvRet,.rvm__foot--act #rvOk{min-height:52px;font-size:16px}.rvm__foot .btn{min-height:44px}.rvm__foot--act .btn{min-width:0}}
904
904
  @media (max-width:560px){.rvm__foot{gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom))}.rvm__foot .btn{flex:1 1 auto;justify-content:center;padding:9px 8px}.rvm__foot .spacer{display:none}.rvm__top{padding:8px 10px}.rvm__top .brand small{display:none}}
905
+ /* a phone while returning a step: the note and its buttons get the room (the decision bar would cover them), the
906
+ close button stays in the first row next to the progress */
907
+ @media (max-width:560px){.rvm--ret .rvm__foot{display:none}.rvm--ret .rvm__body{grid-template-rows:minmax(120px,30%) 1fr}.rvm__top>*{order:1}.rvm__top .brand{order:-4}.rvm__top .rvm__prog{order:-3}.rvm__top .rvm__bar{order:-2}.rvm__top #rvClose{order:-1;margin-left:auto}}
908
+ .verdict-sec textarea{box-sizing:border-box;max-width:100%}
909
+ @media (hover:none) and (pointer:coarse){.rvm__ret .share__row .note{display:none}}
905
910
  @media (hover:none) and (pointer:coarse){.rvm .kbd,.rvm__keys{display:none}}
906
911
  @media (max-width:1400px){#rvBtn span[data-t]{display:none}#rvBtn{padding:5px 8px}}
907
912
  /* batch F: swipe and onion compare, drawn regions, comment drawings and images, the grid, the acceptance record */
@@ -981,7 +986,7 @@ details summary{cursor:pointer;font-size:12.5px;font-weight:500}
981
986
  <span class="task"><b id="taskTitle"></b><i id="liveTag" class="livetag" hidden></i><span id="runMeta"></span></span>
982
987
  <kbd>K</kbd>
983
988
  </button>
984
- <button class="btn" id="rpBtn" title="R"><svg class="i" viewBox="0 0 24 24"><path d="M7 5v14l11-7z"/></svg><span data-t="replay"></span></button>
989
+ <button class="btn" id="rpBtn" title="R" data-t-label="replay"><svg class="i" viewBox="0 0 24 24"><path d="M7 5v14l11-7z"/></svg><span data-t="replay"></span></button>
985
990
  <a class="btn" id="wtBtn" hidden download><svg class="i" viewBox="0 0 24 24"><rect x="3" y="5" width="14" height="14" rx="2"/><path d="m17 10 4-2v8l-4-2"/></svg><span data-t="walkthrough"></span></a>
986
991
  <button class="btn" id="rvBtn" title="Q"><svg class="i" viewBox="0 0 24 24"><path d="M4 6h10M4 12h10M4 18h7"/><path d="m15 17 2.5 2.5L22 15"/></svg><span data-t="rvTitle"></span></button>
987
992
  <button class="btn" id="ptBtn" title="P" hidden><svg class="i" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="13" rx="1.5"/><path d="M8 21h8M12 17v4"/></svg><span data-t="prototype"></span></button>
@@ -1042,7 +1047,7 @@ details summary{cursor:pointer;font-size:12.5px;font-weight:500}
1042
1047
  <div class="br__box">
1043
1048
  <div class="br__top">
1044
1049
  <input type="search" id="brSearch" autocomplete="off">
1045
- <button class="icon-btn" id="brClose" aria-label="Close"><svg class="i" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
1050
+ <button class="icon-btn" id="brClose" aria-label="Close" data-t-label="close"><svg class="i" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
1046
1051
  </div>
1047
1052
  <div class="br__col" id="brScCol">
1048
1053
  <div class="br__filters"><div class="seg" id="brKind"></div><select id="brProject" aria-label="Project"></select><span class="note" id="brCount"></span></div>
@@ -1067,7 +1072,7 @@ details summary{cursor:pointer;font-size:12.5px;font-weight:500}
1067
1072
  const EMBEDDED = /*__QAWALK_RUN__*/null;
1068
1073
  if (!window.QAWALK_APP) { const el = document.getElementById('qawalk-app'); if (el) { try { window.QAWALK_APP = JSON.parse(el.textContent); } catch {} } }
1069
1074
  const I18N = {
1070
- cs: { whatChanged:'Co se změnilo', sinceVersion:'od', notesBy:'Poznámky upravil(a)', notesEdit:'Napsat shrnutí vlastními slovy', notesPrompt:'Co se v této verzi změnilo a proč (uvidí to recenzenti i sledující)', makePublicVersion:'Zveřejnit verzi', draft:'Koncept', liveShared:'živě, sdíleno', publishHint:'Publikování uloží celé flow jako %V; nezměněné kroky se použijí znovu.', htmlView:'HTML pohled', htmlViewNote:'Statická kopie stránky: bez skriptů webu, s přenesenými styly a obrázky, bez tajných údajů.', editTitle:'Upravit na plachtě', editText:'Text', editKeep:'ponechat', editSave:'Uložit úpravu', editSaved:'Úprava uložena jako nová revize', editRebased:'Krok se mezitím změnil; úprava se uložila do nejnovější revize', editUnapplied:'Neuplatněno (prvek už neexistuje)', staleCapture:'Snímek je z předchozí revize; Živě ukazuje poslední úpravu.', branches:'Větve', mergeBranch:'Sloučit do main', mergeConflict:'Krok se změnil v obou větvích:', mergeTakeBranch:'Vzít z větve? (Zrušit = ponechat main)', branchMerged:'Sloučeno', acceptVersion:'Přijmout verzi', returnVersion:'Vrátit verzi', versionVerdict:'Verdikt verze', draftInfo:'Koncept je živý a sdílený: kdo ho má otevřený, vidí změny hned, i ty od agentů. Kroky ukládá kaloko sync nebo úprava na plachtě; tlačítko Publikovat verzi uloží celé flow jako novou verzi.', changedSince:'Změněno od', noChangeSince:'Beze změny od', designEnv:'design', version:'Verze', versions:'Verze flow', publish:'Publikovat verzi', publishName:'Název verze (nepovinný)', published:'Publikováno', designRef:'reference designu', setDesignRef:'Nastavit jako referenci designu', unsetDesignRef:'Zrušit referenci designu', designRefHint:'Implementace se kontroluje proti referenci designu (pack design).', openDraft:'Otevřít koncept', liveMode:'Živě', shotMode:'Snímek', inspect:'Inspektor', pinComment:'Komentář k prvku', pickHint:'Klikněte na prvek v náhledu.', pinnedOn:'K prvku', unchangedSince:'beze změny od', unchangedSteps:'kroků beze změny', changedOnly:'Jen změněné', stepStatus:'Stav kroku', st_in_progress:'rozpracováno', st_ready_for_review:'k revizi', st_ready_for_dev:'připraveno pro vývoj', present:'Prezentovat', presenting:'Prezentujete', stopPresent:'Ukončit prezentaci', updatedSteps:'upravil', viaAgent:'přes', here:'tady', copyCss:'Kopírovat CSS', cssCopied:'CSS zkopírováno', noToken:'mimo sadu tokenů', tokenSet:'token', revisionBy:'revize', noLiveOrigin:'Živé kroky nejsou na této instanci zapnuté.', draftEmpty:'Koncept zatím nemá žádné kroky; spusťte kaloko sync.', prototypeTo:'Prototyp: přechod na', outdated:'zastaralé', prototype:'Prototyp', protoTitle:'Prezentovat prototyp', protoHint:'Klikejte v prototypu; ← → předchozí / další krok, Esc konec.', protoShot:'Jen snímky: živé kroky tu nejsou k dispozici. ← → procházejí flow.', protoLocal:'Odkazy otevírají stránky z disku; ← → procházejí flow.', protoDevice:'Rámeček zařízení', protoFull:'Celá obrazovka', protoExit:'Ukončit prototyp', protoNoStep:'Odkaz nevede na žádný krok flow', focus:'Jen náhled', focusTitle:'Ukázat jen náhled přes celou obrazovku', focusExit:'Zpět do detailu', focusBar:'Ovládání náhledu', focusShow:'Ukázat ovládání', moreTools:'Další nástroje', sheetHandle:'Detail kroku: klepněte nebo táhněte', hint:'Táhněte pro posun · Ctrl + kolečko zoom · <span class="kbd">F</span> režim soustředění (v detailu jen náhled) · <span class="kbd">K</span> scénáře a běhy · <span class="kbd">R</span> projít · <span class="kbd">P</span> prototyp · <span class="kbd">D</span> Dev mode (v detailu) · <span class="kbd">I</span> mapa / index · <span class="kbd">←</span><span class="kbd">→</span> předchozí / další krok (v detailu) · <span class="kbd">Esc</span> zavřít', locale:'Testovaný jazyk', view:'Náhled', both:'Oba', shownRun:'Zobrazený běh', allRuns:'Všechny běhy', steps:'Kroky flow', fit:'Celé flow', stepsWord:'kroků', passedWord:'prošlo', fitLane:'Zobrazit celou sekci', following:'Sledujete', followEsc:'Esc ukončí', followHint:'Sledovat, co dělá', visOn:'Vidí mě ostatní', visOff:'Jsem skrytý',
1075
+ cs: { whatChanged:'Co se změnilo', sinceVersion:'od', notesBy:'Poznámky upravil(a)', notesEdit:'Napsat shrnutí vlastními slovy', notesPrompt:'Co se v této verzi změnilo a proč (uvidí to recenzenti i sledující)', makePublicVersion:'Zveřejnit verzi', draft:'Koncept', liveShared:'živě, sdíleno', publishHint:'Publikování uloží celý scénář jako %V; nezměněné kroky se použijí znovu.', htmlView:'HTML pohled', htmlViewNote:'Statická kopie stránky: bez skriptů webu, s přenesenými styly a obrázky, bez tajných údajů.', editTitle:'Upravit na plachtě', editText:'Text', editKeep:'ponechat', editSave:'Uložit úpravu', editSaved:'Úprava uložena jako nová revize', editRebased:'Krok se mezitím změnil; úprava se uložila do nejnovější revize', editUnapplied:'Neuplatněno (prvek už neexistuje)', staleCapture:'Snímek je z předchozí revize; Živě ukazuje poslední úpravu.', branches:'Větve', mergeBranch:'Sloučit do main', mergeConflict:'Krok se změnil v obou větvích:', mergeTakeBranch:'Vzít z větve? (Zrušit = ponechat main)', branchMerged:'Sloučeno', acceptVersion:'Přijmout verzi', returnVersion:'Vrátit verzi', versionVerdict:'Verdikt verze', draftInfo:'Koncept je živý a sdílený: kdo ho má otevřený, vidí změny hned, i ty od agentů. Kroky ukládá kaloko sync nebo úprava na plachtě; tlačítko Publikovat verzi uloží celý scénář jako novou verzi.', changedSince:'Změněno od', noChangeSince:'Beze změny od', designEnv:'design', version:'Verze', versions:'Verze scénáře', publish:'Publikovat verzi', publishName:'Název verze (nepovinný)', published:'Publikováno', designRef:'reference designu', setDesignRef:'Nastavit jako referenci designu', unsetDesignRef:'Zrušit referenci designu', designRefHint:'Implementace se kontroluje proti referenci designu (pack design).', openDraft:'Otevřít koncept', liveMode:'Živě', shotMode:'Snímek', inspect:'Inspektor', pinComment:'Komentář k prvku', pickHint:'Klikněte na prvek v náhledu.', pinnedOn:'K prvku', unchangedSince:'beze změny od', unchangedSteps:'kroků beze změny', changedOnly:'Jen změněné', stepStatus:'Stav kroku', st_in_progress:'rozpracováno', st_ready_for_review:'k revizi', st_ready_for_dev:'připraveno pro vývoj', present:'Prezentovat', presenting:'Prezentujete', stopPresent:'Ukončit prezentaci', updatedSteps:'upravil', viaAgent:'přes', here:'tady', copyCss:'Kopírovat CSS', cssCopied:'CSS zkopírováno', noToken:'mimo sadu tokenů', tokenSet:'token', revisionBy:'revize', noLiveOrigin:'Živé kroky nejsou na této instanci zapnuté.', draftEmpty:'Koncept zatím nemá žádné kroky; spusťte kaloko sync.', prototypeTo:'Prototyp: přechod na', outdated:'zastaralé', prototype:'Prototyp', protoTitle:'Prezentovat prototyp', protoHint:'Klikejte v prototypu; ← → předchozí / další krok, Esc konec.', protoShot:'Jen snímky: živé kroky tu nejsou k dispozici. ← → procházejí scénář.', protoLocal:'Odkazy otevírají stránky z disku; ← → procházejí scénář.', protoDevice:'Rámeček zařízení', protoFull:'Celá obrazovka', protoExit:'Ukončit prototyp', protoNoStep:'Odkaz nevede na žádný krok scénáře', focus:'Jen náhled', focusTitle:'Ukázat jen náhled přes celou obrazovku', focusExit:'Zpět do detailu', focusBar:'Ovládání náhledu', focusShow:'Ukázat ovládání', moreTools:'Další nástroje', sheetHandle:'Detail kroku: klepněte nebo táhněte', hint:'Táhněte pro posun · Ctrl + kolečko zoom · <span class="kbd">F</span> režim soustředění (v detailu jen náhled) · <span class="kbd">K</span> scénáře a běhy · <span class="kbd">R</span> projít · <span class="kbd">P</span> prototyp · <span class="kbd">D</span> Dev mode (v detailu) · <span class="kbd">I</span> mapa / index · <span class="kbd">←</span><span class="kbd">→</span> předchozí / další krok (v detailu) · <span class="kbd">Esc</span> zavřít', locale:'Testovaný jazyk', view:'Náhled', both:'Oba', shownRun:'Zobrazený běh', allRuns:'Všechny běhy', steps:'Kroky scénáře', fit:'Celý scénář', stepsWord:'kroků', passedWord:'prošlo', fitLane:'Zobrazit celou sekci', following:'Sledujete', followEsc:'Esc ukončí', followHint:'Sledovat, co dělá', visOn:'Vidí mě ostatní', visOff:'Jsem skrytý',
1071
1076
  pass:'Splněno', partial:'Částečně', fail:'Nesplněno', skip:'Neprověřeno', crop:'výřez', html:'HTML snapshot', email:'e-mail', external:'třetí strana',
1072
1077
  branch:'Větev', commit:'Commit', author:'Autor', pr:'PR', issue:'Issue', env:'Prostředí', duration:'Délka', noPr:'bez PR', open:'otevřený', merged:'sloučený', closed:'zavřený',
1073
1078
  older:'Starší běh', newer:'Novější běh', purpose:'Účel obrazovky ze scénáře', criteria:'Akceptační kritéria', jevTitle:'Sémantické hodnocení',
@@ -1088,12 +1093,12 @@ const I18N = {
1088
1093
  allAbove:'Všechny otázky nad prahem', review:'k revizi', notEvaluated:'nehodnoceno', search:'Hledat scénář, task, cestu…', all:'Vše', tasks:'Úkoly', flows:'Flow',
1089
1094
  runs:'běhů', last:'poslední', time:'Čas', result:'Výsledek', noRuns:'Žádný běh neodpovídá filtrům.', noScenario:'Žádný scénář neodpovídá hledání.',
1090
1095
  onlyLocal:'Tento běh je jen lokálně; otevřete jeho složku.', linksExpired:'Odkazy na snímky vypršely. Obnovte stránku.', captured:'Zachyceno', console:'Konzole', errors:'chyb', blocked:'blokovaných požadavků', files:'Soubory',
1091
- pagesWord:'stránek', readOnlyVisit:'Organizaci procházíte jen pro čtení, takže komentovat nemůžete. Pokud v ní jste členem, přepněte se do ní v hlavičce.', firstScreen:'první obrazovka', layout:'Rozložení', uploadedBy:'Nahrál', addNote:'Přidat poznámku', viewOptions:'Zobrazení', keyComment:'komentář ke kroku (v detailu)', keyHelp:'tato nápověda', helpTitle:'Zkratky a nápověda', flowComments:'Komentáře k flow', runComments:'Komentáře k běhu', commentsTitle:'Komentáře', allOpen:'Otevřené', openWord:'otevřených', flowCommentsLead:'Platí pro celé flow napříč běhy: chybějící krok, obecná připomínka, nápad.', noStepComments:'Ke kroku zatím nikdo nic nenapsal.', noComments:'Zatím žádné komentáře.', showResolved:'Vyřešené', hideResolved:'Skrýt vyřešené', replyAct:'Odpovědět', reopenAct:'Znovu otevřít', editAct:'Upravit', deleteAct:'Smazat', saveAct:'Uložit', cancel:'Zrušit', edited:'upraveno', commentDeleted:'Komentář byl smazán.', agentWrote:'Napsal agent', showPin:'Ukázat na snímku', pinOnShot:'Připnout na snímek', pinClickShot:'Klikněte do snímku', pinnedOnShot:'Připnuto na snímek', replyPh:'Odpověď… (@jméno upozorní)', flowPh:'Komentář k celému flow… (@jméno upozorní)', runPh:'Komentář k celému běhu… (@jméno upozorní)', commentLabel:'Komentář', ctrlEnter:'Ctrl+Enter odešle', confirmDelete:'Smazat komentář?', deletedToast:'Smazáno', openCommentsN:'Nevyřízené komentáře', showAllN:'Zobrazit všech %N%', screenWord:'obrazovka aplikace', uiTree:'UI strom', platformWord:'Platforma', treeHint:'Klikněte na prvek: ukáže ho na snímku.', treeSearch:'Hledat v UI stromu…', treeOcr:'Text rozpoznaný ze snímku (OCR): bez rolí a ovládacích prvků.', treeLoadFail:'UI strom se nepodařilo načíst.', treeEmpty:'Strom je prázdný.', mapView:'Mapa', indexView:'Index', searchSteps:'Hledat v krocích, kritériích a stránkách…', criteriaCol:'Kritéria', notesCol:'Poznámky', noStepsMatch:'Žádný krok neodpovídá filtru.', riKeys:'/ hledat · ↑↓ vybrat · Enter otevřít · I mapa', consoleWord:'chyb v konzoli', loadingPages:'hledám i ve stránkách stacků…', sortTitle:'Název', runWord:'Běh', openIndex:'Otevřít index', pagesNeedAttention:'stránek chce pozornost', allPagesPass:'všechny stránky prošly', stackLoadFail:'Index stránek se nepodařilo načíst', semanticSample:'sémantický vzorek', downloadCsv:'Stáhnout CSV', allWord:'Vše', criterionFilter:'Nesplněné kritérium', anyCriterion:'Jakékoli kritérium', searchPages:'Hledat v titulku, URL a polích…', sortBy:'Řadit podle', sortOrder:'Pořadí', sortStatus:'Stav (nejhorší první)', sortDir:'Směr řazení', pageWord:'Stránka', statusWord:'Stav', failingCriteria:'Neprošlo', stackKeys:'/ hledat · ↑↓ vybrat · Enter otevřít · Esc zavřít', listView:'Seznam', gridView:'Mřížka', noPagesMatch:'Filtru neodpovídá žádná stránka.', backToIndex:'Zpět na index',
1096
+ pagesWord:'stránek', readOnlyVisit:'Organizaci procházíte jen pro čtení, takže komentovat nemůžete. Pokud v ní jste členem, přepněte se do ní v hlavičce.', firstScreen:'první obrazovka', layout:'Rozložení', uploadedBy:'Nahrál', addNote:'Přidat poznámku', viewOptions:'Zobrazení', keyComment:'komentář ke kroku (v detailu)', keyHelp:'tato nápověda', helpTitle:'Zkratky a nápověda', flowComments:'Komentáře ke scénáři', runComments:'Komentáře k běhu', commentsTitle:'Komentáře', allOpen:'Otevřené', openWord:'otevřených', flowCommentsLead:'Platí pro celý scénář napříč běhy: chybějící krok, obecná poznámka, nápad.', noStepComments:'Ke kroku zatím nikdo nic nenapsal.', noComments:'Zatím žádné komentáře.', showResolved:'Vyřešené', hideResolved:'Skrýt vyřešené', replyAct:'Odpovědět', reopenAct:'Znovu otevřít', editAct:'Upravit', deleteAct:'Smazat', saveAct:'Uložit', cancel:'Zrušit', edited:'upraveno', commentDeleted:'Komentář byl smazán.', agentWrote:'Napsal agent', showPin:'Ukázat na snímku', pinOnShot:'Připnout na snímek', pinClickShot:'Klikněte do snímku', pinnedOnShot:'Připnuto na snímek', replyPh:'Odpověď… (@jméno upozorní)', flowPh:'Komentář k celému scénáři… (@jméno upozorní)', runPh:'Komentář k celému běhu… (@jméno upozorní)', commentLabel:'Komentář', ctrlEnter:'Ctrl+Enter odešle', confirmDelete:'Smazat komentář?', deletedToast:'Smazáno', openCommentsN:'Nevyřízené komentáře', showAllN:'Zobrazit všech %N%', screenWord:'obrazovka aplikace', uiTree:'UI strom', platformWord:'Platforma', treeHint:'Klikněte na prvek: ukáže ho na snímku.', treeSearch:'Hledat v UI stromu…', treeOcr:'Text rozpoznaný ze snímku (OCR): bez rolí a ovládacích prvků.', treeLoadFail:'UI strom se nepodařilo načíst.', treeEmpty:'Strom je prázdný.', mapView:'Mapa', indexView:'Index', searchSteps:'Hledat v krocích, kritériích a stránkách…', criteriaCol:'Kritéria', notesCol:'Poznámky', noStepsMatch:'Žádný krok neodpovídá filtru.', riKeys:'/ hledat · ↑↓ vybrat · Enter otevřít · I mapa', consoleWord:'chyb v konzoli', loadingPages:'hledám i ve stránkách stacků…', sortTitle:'Název', runWord:'Běh', openIndex:'Otevřít index', pagesNeedAttention:'stránek chce pozornost', allPagesPass:'všechny stránky prošly', stackLoadFail:'Index stránek se nepodařilo načíst', semanticSample:'sémantický vzorek', downloadCsv:'Stáhnout CSV', allWord:'Vše', criterionFilter:'Nesplněné kritérium', anyCriterion:'Jakékoli kritérium', searchPages:'Hledat v titulku, URL a polích…', sortBy:'Řadit podle', sortOrder:'Pořadí', sortStatus:'Stav (nejhorší první)', sortDir:'Směr řazení', pageWord:'Stránka', statusWord:'Stav', failingCriteria:'Neprošlo', stackKeys:'/ hledat · ↑↓ vybrat · Enter otevřít · Esc zavřít', listView:'Seznam', gridView:'Mřížka', noPagesMatch:'Filtru neodpovídá žádná stránka.', backToIndex:'Zpět na index',
1092
1097
  subject:'Předmět', from:'Od', to:'Komu', received:'Přijato', mailBeforeRun:'přišel před začátkem běhu', noCapture:'Bez snímku', viewEval:'eval.json', decision:'rozhodnutí', mainLane:'Hlavní průchod',
1093
- downloadZip:'Stáhnout ZIP', downloadZipNote:'Všechny soubory v otevřených formátech a plachta, která se otevře offline z disku', filesWord:'souborů', publicAccess:'veřejný odkaz', orgAccess:'přístup po přihlášení do organizace', projectAll:'Všechny projekty', packNote:'Kritérium z balíčku kontrol (packs ve scénáři)', coverage:'Pokrytí plánu', coverageLine:'kritérií z ACCEPTANCE.md má kontrolu', coverageExtra:'kontrol mimo plán', coverageFull:'Každé plánované kritérium má kontrolu.', coverageSteps:'Kroky v plánu bez uzlu', flakyTitle:'Nestabilní kritéria', flaky:'nestabilní', flakyNote:'Verdikt se liší mezi běhy na stejném commitu a prostředí.', people:'Lidé ve flow', peopleHint:'Kliknutím na osobu zvýrazníte její kroky; přerušované šipky ukazují předání mezi lidmi.', awaiting:'Čeká na schválení od', approvedBy:'schválili', approvedByAll:'Schválili všichni povinní schvalovatelé.', viaToken:'přes token', approvedByTokens:'přes token nebo agenta', publicRun:'Veřejný běh', publicNote:'Plachta je čitelná bez přihlášení; jména hodnotitelů jsou skrytá.', makePublic:'Zveřejnit běh', makePrivate:'Zrušit zveřejnění', publicHint:'Jen pro scénáře bez osobních údajů', publicConfirm:'Zveřejnit tento běh? Uvidí ho kdokoli s odkazem, bez přihlášení. Použijte jen u scénářů bez osobních údajů.', openInApp:'Otevřít v Kaloku', signInLink:'Přihlásit', replay:'Projít', rpStep:'Krok', rpNext:'Další', rpPrev:'Zpět', rpEnd:'Konec průchodu.', rpRestart:'Znovu od začátku', rpChoose:'Kudy dál?', rpOr:'nebo', rpKeys:'← → krok · čísla volí větev · Esc zavře', rpDecision:'Rozhodnutí', rpBranch:'větev', rpAltOnly:'jiná větev',
1098
+ downloadZip:'Stáhnout ZIP', downloadZipNote:'Všechny soubory v otevřených formátech a plachta, která se otevře offline z disku', filesWord:'souborů', publicAccess:'veřejný odkaz', orgAccess:'přístup po přihlášení do organizace', projectAll:'Všechny projekty', packNote:'Kritérium z balíčku kontrol (packs ve scénáři)', coverage:'Pokrytí plánu', coverageLine:'kritérií z ACCEPTANCE.md má kontrolu', coverageExtra:'kontrol mimo plán', coverageFull:'Každé plánované kritérium má kontrolu.', coverageSteps:'Kroky v plánu bez uzlu', flakyTitle:'Nestabilní kritéria', flaky:'nestabilní', flakyNote:'Verdikt se liší mezi běhy na stejném commitu a prostředí.', people:'Lidé ve scénáři', peopleHint:'Kliknutím na osobu zvýrazníte její kroky; přerušované šipky ukazují předání mezi lidmi.', awaiting:'Čeká na schválení od', approvedBy:'schválili', approvedByAll:'Schválili všichni povinní schvalovatelé.', viaToken:'přes token', approvedByTokens:'přes token nebo agenta', publicRun:'Veřejný běh', publicNote:'Plachta je čitelná bez přihlášení; jména hodnotitelů jsou skrytá.', makePublic:'Zveřejnit běh', makePrivate:'Zrušit zveřejnění', publicHint:'Jen pro scénáře bez osobních údajů', publicConfirm:'Zveřejnit tento běh? Uvidí ho kdokoli s odkazem, bez přihlášení. Použijte jen u scénářů bez osobních údajů.', openInApp:'Otevřít v Kaloku', signInLink:'Přihlásit', replay:'Projít', rpStep:'Krok', rpNext:'Další', rpPrev:'Zpět', rpEnd:'Konec průchodu.', rpRestart:'Znovu od začátku', rpChoose:'Kudy dál?', rpOr:'nebo', rpKeys:'← → krok · čísla volí větev · Esc zavře', rpDecision:'Rozhodnutí', rpBranch:'větev', rpAltOnly:'jiná větev',
1094
1099
  compare:'Porovnat', cmpWith:'Porovnáno s', cmpStop:'Ukončit porovnání', cmpOff:'nic', cmpBaseline:'referenční', cmpPrev:'předchozí', cmpTitle:'Rozdíly oproti', cmpNone:'Statusy, verdikty i evidence jsou stejné.', pxChanged:'pixelů jinak', cmpSide:'Vedle sebe', cmpDiff:'Rozdíl', cmpLoadErr:'Porovnávaný běh se nepodařilo načíst.', pxUnavail:'Pixelový rozdíl se počítá jen ve službě (snímky ze stejného původu).', was:'předtím', thisRun:'tento běh', cmpNoCap:'v porovnávaném běhu bez snímku', cmpAdded:'nové kroky', cmpRemoved:'kroky, které zmizely', cmpHint:'Ctrl/⌘ + klik na krok otevře porovnání',
1095
- reviewTitle:'Akceptace a komentáře', approve:'Schválit', reject:'Zamítnout', comment:'Komentovat', notePh:'Co je potřeba změnit, nebo poznámka · @jméno pošle kolegovi upozornění', approved:'schváleno', rejected:'zamítnuto', commented:'komentář', resolved:'vyřešeno', resolve:'Vyřešit', openFeedback:'Otevřená zpětná vazba', noFeedback:'Žádná otevřená zpětná vazba.', saved:'Uloženo', noteRequired:'Napište, co je potřeba změnit.', expires:'Platí do' },
1096
- en: { whatChanged:'What changed', sinceVersion:'since', notesBy:'Notes edited by', notesEdit:'Write the summary in your own words', notesPrompt:'What changed in this version and why (reviewers and followers see it)', makePublicVersion:'Make version public', draft:'Draft', liveShared:'live, shared', publishHint:'Publish version saves the whole flow as %V; unchanged steps are reused.', htmlView:'HTML view', htmlViewNote:'A static copy of the page: the site’s scripts removed, styles and images rehosted, secrets stripped.', editTitle:'Edit in the canvas', editText:'Text', editKeep:'keep', editSave:'Save edit', editSaved:'Edit saved as a new revision', editRebased:'The step changed meanwhile; the edit is on the latest revision', editUnapplied:'Not applied (the element no longer exists)', staleCapture:'The screenshot is from the previous revision; Live shows the latest edit.', branches:'Branches', mergeBranch:'Merge into main', mergeConflict:'Step changed on both branches:', mergeTakeBranch:'Take the branch version? (Cancel = keep main)', branchMerged:'Merged', acceptVersion:'Accept version', returnVersion:'Return version', versionVerdict:'Version verdict', draftInfo:'This draft is live and shared: everyone who has it open sees changes as they happen, including the agents’. kaloko sync and canvas edits save steps; Publish version saves the whole flow as a new version.', changedSince:'Changed since', noChangeSince:'No change since', designEnv:'design', version:'Version', versions:'Flow versions', publish:'Publish version', publishName:'Version name (optional)', published:'Published', designRef:'design reference', setDesignRef:'Set as design reference', unsetDesignRef:'Unset design reference', designRefHint:'Implementation is checked against the design reference (pack design).', openDraft:'Open the draft', liveMode:'Live', shotMode:'Screenshot', inspect:'Inspect', pinComment:'Comment on element', pickHint:'Click an element in the preview.', pinnedOn:'On element', unchangedSince:'unchanged since', unchangedSteps:'steps unchanged', changedOnly:'Changed only', stepStatus:'Step status', st_in_progress:'in progress', st_ready_for_review:'ready for review', st_ready_for_dev:'ready for dev', present:'Present', presenting:'You are presenting', stopPresent:'Stop presenting', updatedSteps:'updated', viaAgent:'via', here:'here', copyCss:'Copy CSS', cssCopied:'CSS copied', noToken:'outside the token set', tokenSet:'token', revisionBy:'revision', noLiveOrigin:'Live steps are not enabled on this instance.', draftEmpty:'The draft has no steps yet; run kaloko sync.', prototypeTo:'Prototype: go to', outdated:'outdated', prototype:'Prototype', protoTitle:'Present the prototype', protoHint:'Click through the prototype; ← → previous / next step, Esc to leave.', protoShot:'Screenshots only: live steps are not available here. ← → move through the flow.', protoLocal:'Links open the pages from disk; ← → move through the flow.', protoDevice:'Device frame', protoFull:'Full screen', protoExit:'Leave the prototype', protoNoStep:'The link does not lead to a step of the flow', focus:'Focus', focusTitle:'Show only the preview, full screen', focusExit:'Back to the detail', focusBar:'Preview controls', focusShow:'Show controls', moreTools:'More tools', sheetHandle:'Step detail: tap or drag', hint:'Drag to pan · Ctrl + wheel to zoom · <span class="kbd">F</span> focus mode (in the detail: the preview only) · <span class="kbd">K</span> scenarios and runs · <span class="kbd">R</span> replay · <span class="kbd">P</span> prototype · <span class="kbd">D</span> Dev mode (in the detail) · <span class="kbd">I</span> map / index · <span class="kbd">←</span><span class="kbd">→</span> previous / next step (in the detail) · <span class="kbd">Esc</span> close', locale:'Tested locale', view:'View', both:'Both', shownRun:'Shown run', allRuns:'All runs', steps:'Flow steps', fit:'Fit flow', stepsWord:'steps', passedWord:'passed', fitLane:'Show the whole section', following:'Following', followEsc:'Esc to stop', followHint:'Follow what they do', visOn:'Others see me', visOff:'Hidden',
1100
+ reviewTitle:'Schválení a komentáře', approve:'Schválit', reject:'Vrátit', comment:'Komentovat', notePh:'Co je potřeba změnit, nebo poznámka · @jméno pošle kolegovi upozornění', approved:'schváleno', rejected:'vráceno', commented:'komentář', resolved:'vyřešeno', resolve:'Vyřešit', openFeedback:'Otevřená zpětná vazba', noFeedback:'Žádná otevřená zpětná vazba.', saved:'Uloženo', noteRequired:'Napište, co je potřeba změnit.', expires:'Platí do' },
1101
+ en: { whatChanged:'What changed', sinceVersion:'since', notesBy:'Notes edited by', notesEdit:'Write the summary in your own words', notesPrompt:'What changed in this version and why (reviewers and followers see it)', makePublicVersion:'Make version public', draft:'Draft', liveShared:'live, shared', publishHint:'Publish version saves the whole scenario as %V; unchanged steps are reused.', htmlView:'HTML view', htmlViewNote:'A static copy of the page: the site’s scripts removed, styles and images rehosted, secrets stripped.', editTitle:'Edit in the canvas', editText:'Text', editKeep:'keep', editSave:'Save edit', editSaved:'Edit saved as a new revision', editRebased:'The step changed meanwhile; the edit is on the latest revision', editUnapplied:'Not applied (the element no longer exists)', staleCapture:'The screenshot is from the previous revision; Live shows the latest edit.', branches:'Branches', mergeBranch:'Merge into main', mergeConflict:'Step changed on both branches:', mergeTakeBranch:'Take the branch version? (Cancel = keep main)', branchMerged:'Merged', acceptVersion:'Accept version', returnVersion:'Return version', versionVerdict:'Version verdict', draftInfo:'This draft is live and shared: everyone who has it open sees changes as they happen, including the agents’. kaloko sync and canvas edits save steps; Publish version saves the whole scenario as a new version.', changedSince:'Changed since', noChangeSince:'No change since', designEnv:'design', version:'Version', versions:'Scenario versions', publish:'Publish version', publishName:'Version name (optional)', published:'Published', designRef:'design reference', setDesignRef:'Set as design reference', unsetDesignRef:'Unset design reference', designRefHint:'Implementation is checked against the design reference (pack design).', openDraft:'Open the draft', liveMode:'Live', shotMode:'Screenshot', inspect:'Inspect', pinComment:'Comment on element', pickHint:'Click an element in the preview.', pinnedOn:'On element', unchangedSince:'unchanged since', unchangedSteps:'steps unchanged', changedOnly:'Changed only', stepStatus:'Step status', st_in_progress:'in progress', st_ready_for_review:'ready for review', st_ready_for_dev:'ready for dev', present:'Present', presenting:'You are presenting', stopPresent:'Stop presenting', updatedSteps:'updated', viaAgent:'via', here:'here', copyCss:'Copy CSS', cssCopied:'CSS copied', noToken:'outside the token set', tokenSet:'token', revisionBy:'revision', noLiveOrigin:'Live steps are not enabled on this instance.', draftEmpty:'The draft has no steps yet; run kaloko sync.', prototypeTo:'Prototype: go to', outdated:'outdated', prototype:'Prototype', protoTitle:'Present the prototype', protoHint:'Click through the prototype; ← → previous / next step, Esc to leave.', protoShot:'Screenshots only: live steps are not available here. ← → move through the scenario.', protoLocal:'Links open the pages from disk; ← → move through the scenario.', protoDevice:'Device frame', protoFull:'Full screen', protoExit:'Leave the prototype', protoNoStep:'The link does not lead to a step of the scenario', focus:'Focus', focusTitle:'Show only the preview, full screen', focusExit:'Back to the detail', focusBar:'Preview controls', focusShow:'Show controls', moreTools:'More tools', sheetHandle:'Step detail: tap or drag', hint:'Drag to pan · Ctrl + wheel to zoom · <span class="kbd">F</span> focus mode (in the detail: the preview only) · <span class="kbd">K</span> scenarios and runs · <span class="kbd">R</span> replay · <span class="kbd">P</span> prototype · <span class="kbd">D</span> Dev mode (in the detail) · <span class="kbd">I</span> map / index · <span class="kbd">←</span><span class="kbd">→</span> previous / next step (in the detail) · <span class="kbd">Esc</span> close', locale:'Tested locale', view:'View', both:'Both', shownRun:'Shown run', allRuns:'All runs', steps:'Scenario steps', fit:'Whole scenario', stepsWord:'steps', passedWord:'passed', fitLane:'Show the whole section', following:'Following', followEsc:'Esc to stop', followHint:'Follow what they do', visOn:'Others see me', visOff:'Hidden',
1097
1102
  pass:'Passed', partial:'Partial', fail:'Failed', skip:'Not checked', crop:'crop', html:'HTML snapshot', email:'e-mail', external:'third party',
1098
1103
  branch:'Branch', commit:'Commit', author:'Author', pr:'PR', issue:'Issue', env:'Environment', duration:'Duration', noPr:'no PR', open:'open', merged:'merged', closed:'closed',
1099
1104
  older:'Older run', newer:'Newer run', purpose:'Screen purpose from the scenario', criteria:'Acceptance criteria', jevTitle:'Semantic evaluation',
@@ -1114,11 +1119,11 @@ const I18N = {
1114
1119
  allAbove:'All questions above threshold', review:'needs review', notEvaluated:'not evaluated', search:'Search scenario, task, path…', all:'All', tasks:'Tasks', flows:'Flows',
1115
1120
  runs:'runs', last:'last', time:'Time', result:'Result', noRuns:'No run matches the filters.', noScenario:'No scenario matches.',
1116
1121
  onlyLocal:'This run exists locally only; open its folder.', linksExpired:'Links to the captures expired. Reload the page.', captured:'Captured', console:'Console', errors:'errors', blocked:'blocked requests', files:'Files',
1117
- pagesWord:'pages', readOnlyVisit:'You are visiting this organization read-only, so you cannot comment. If you are a member, switch to it in the header.', firstScreen:'first screen', layout:'Layout', uploadedBy:'Uploaded by', addNote:'Add a note', viewOptions:'View', keyComment:'comment on the step (in the detail)', keyHelp:'this help', helpTitle:'Shortcuts and help', flowComments:'Flow comments', runComments:'Run comments', commentsTitle:'Comments', allOpen:'Open', openWord:'open', flowCommentsLead:'For the whole flow across runs: a missing step, a general remark, an idea.', noStepComments:'No comments on this step yet.', noComments:'No comments yet.', showResolved:'Resolved', hideResolved:'Hide resolved', replyAct:'Reply', reopenAct:'Reopen', editAct:'Edit', deleteAct:'Delete', saveAct:'Save', cancel:'Cancel', edited:'edited', commentDeleted:'This comment was deleted.', agentWrote:'Written by an agent', showPin:'Show on the screenshot', pinOnShot:'Pin on the screenshot', pinClickShot:'Click on the screenshot', pinnedOnShot:'Pinned on the screenshot', replyPh:'Reply… (@name notifies)', flowPh:'Comment on the whole flow… (@name notifies)', runPh:'Comment on the whole run… (@name notifies)', commentLabel:'Comment', ctrlEnter:'Ctrl+Enter sends', confirmDelete:'Delete this comment?', deletedToast:'Deleted', openCommentsN:'Open comments', showAllN:'Show all %N%', screenWord:'app screen', uiTree:'UI tree', platformWord:'Platform', treeHint:'Click an element to show it on the screen.', treeSearch:'Search the UI tree…', treeOcr:'Text recognized from the screenshot (OCR): no roles or controls.', treeLoadFail:'The UI tree could not be loaded.', treeEmpty:'The tree is empty.', mapView:'Map', indexView:'Index', searchSteps:'Search steps, criteria and pages…', criteriaCol:'Criteria', notesCol:'Notes', noStepsMatch:'No step matches the filter.', riKeys:'/ search · ↑↓ select · Enter open · I map', consoleWord:'console errors', loadingPages:'also searching the pages of stacks…', sortTitle:'Title', runWord:'Run', openIndex:'Open index', pagesNeedAttention:'pages need attention', allPagesPass:'all pages pass', stackLoadFail:'The page index could not be loaded', semanticSample:'semantic sample', downloadCsv:'Download CSV', allWord:'All', criterionFilter:'Failing criterion', anyCriterion:'Any criterion', searchPages:'Search title, URL and fields…', sortBy:'Sort by', sortOrder:'Order', sortStatus:'Status (worst first)', sortDir:'Sort direction', pageWord:'Page', statusWord:'Status', failingCriteria:'Failing', stackKeys:'/ search · ↑↓ select · Enter open · Esc close', listView:'List', gridView:'Grid', noPagesMatch:'No page matches the filter.', backToIndex:'Back to index',
1122
+ pagesWord:'pages', readOnlyVisit:'You are visiting this organization read-only, so you cannot comment. If you are a member, switch to it in the header.', firstScreen:'first screen', layout:'Layout', uploadedBy:'Uploaded by', addNote:'Add a note', viewOptions:'View', keyComment:'comment on the step (in the detail)', keyHelp:'this help', helpTitle:'Shortcuts and help', flowComments:'Scenario comments', runComments:'Run comments', commentsTitle:'Comments', allOpen:'Open', openWord:'open', flowCommentsLead:'For the whole scenario across runs: a missing step, a general remark, an idea.', noStepComments:'No comments on this step yet.', noComments:'No comments yet.', showResolved:'Resolved', hideResolved:'Hide resolved', replyAct:'Reply', reopenAct:'Reopen', editAct:'Edit', deleteAct:'Delete', saveAct:'Save', cancel:'Cancel', edited:'edited', commentDeleted:'This comment was deleted.', agentWrote:'Written by an agent', showPin:'Show on the screenshot', pinOnShot:'Pin on the screenshot', pinClickShot:'Click on the screenshot', pinnedOnShot:'Pinned on the screenshot', replyPh:'Reply… (@name notifies)', flowPh:'Comment on the whole scenario… (@name notifies)', runPh:'Comment on the whole run… (@name notifies)', commentLabel:'Comment', ctrlEnter:'Ctrl+Enter sends', confirmDelete:'Delete this comment?', deletedToast:'Deleted', openCommentsN:'Open comments', showAllN:'Show all %N%', screenWord:'app screen', uiTree:'UI tree', platformWord:'Platform', treeHint:'Click an element to show it on the screen.', treeSearch:'Search the UI tree…', treeOcr:'Text recognized from the screenshot (OCR): no roles or controls.', treeLoadFail:'The UI tree could not be loaded.', treeEmpty:'The tree is empty.', mapView:'Map', indexView:'Index', searchSteps:'Search steps, criteria and pages…', criteriaCol:'Criteria', notesCol:'Notes', noStepsMatch:'No step matches the filter.', riKeys:'/ search · ↑↓ select · Enter open · I map', consoleWord:'console errors', loadingPages:'also searching the pages of stacks…', sortTitle:'Title', runWord:'Run', openIndex:'Open index', pagesNeedAttention:'pages need attention', allPagesPass:'all pages pass', stackLoadFail:'The page index could not be loaded', semanticSample:'semantic sample', downloadCsv:'Download CSV', allWord:'All', criterionFilter:'Failing criterion', anyCriterion:'Any criterion', searchPages:'Search title, URL and fields…', sortBy:'Sort by', sortOrder:'Order', sortStatus:'Status (worst first)', sortDir:'Sort direction', pageWord:'Page', statusWord:'Status', failingCriteria:'Failing', stackKeys:'/ search · ↑↓ select · Enter open · Esc close', listView:'List', gridView:'Grid', noPagesMatch:'No page matches the filter.', backToIndex:'Back to index',
1118
1123
  subject:'Subject', from:'From', to:'To', received:'Received', mailBeforeRun:'arrived before the run started', noCapture:'No capture', viewEval:'eval.json', decision:'decision', mainLane:'Main path',
1119
- downloadZip:'Download ZIP', downloadZipNote:'Every file in open formats and the canvas, which opens offline from disk', filesWord:'files', publicAccess:'public link', orgAccess:'access after signing in to the organization', projectAll:'All projects', packNote:'Criterion from a check pack (packs in the scenario)', coverage:'Plan coverage', coverageLine:'criteria from ACCEPTANCE.md have a check', coverageExtra:'checks outside the plan', coverageFull:'Every planned criterion has a check.', coverageSteps:'Steps in the plan without a node', flakyTitle:'Flaky criteria', flaky:'flaky', flakyNote:'The verdict differs between runs on the same commit and environment.', people:'People in the flow', peopleHint:'Click a person to highlight their steps; dashed arrows are hand-offs between people.', awaiting:'Awaiting approval from', approvedBy:'approved by', approvedByAll:'Every required approver has approved.', viaToken:'by token', approvedByTokens:'by token or agent', publicRun:'Public run', publicNote:'The canvas is readable without sign-in; reviewer names are hidden.', makePublic:'Make public', makePrivate:'Make private', publicHint:'Only for scenarios without personal data', publicConfirm:'Make this run public? Anyone with the link can read it without signing in. Use it only for scenarios without personal data.', openInApp:'Open in Kaloko', signInLink:'Sign in', replay:'Replay', rpStep:'Step', rpNext:'Next', rpPrev:'Back', rpEnd:'End of the walkthrough.', rpRestart:'Start over', rpChoose:'Which way?', rpOr:'or', rpKeys:'← → step · digits pick a branch · Esc closes', rpDecision:'Decision', rpBranch:'branch', rpAltOnly:'other branch',
1124
+ downloadZip:'Download ZIP', downloadZipNote:'Every file in open formats and the canvas, which opens offline from disk', filesWord:'files', publicAccess:'public link', orgAccess:'access after signing in to the organization', projectAll:'All projects', packNote:'Criterion from a check pack (packs in the scenario)', coverage:'Plan coverage', coverageLine:'criteria from ACCEPTANCE.md have a check', coverageExtra:'checks outside the plan', coverageFull:'Every planned criterion has a check.', coverageSteps:'Steps in the plan without a node', flakyTitle:'Flaky criteria', flaky:'flaky', flakyNote:'The verdict differs between runs on the same commit and environment.', people:'People in the scenario', peopleHint:'Click a person to highlight their steps; dashed arrows are hand-offs between people.', awaiting:'Awaiting approval from', approvedBy:'approved by', approvedByAll:'Every required approver has approved.', viaToken:'by token', approvedByTokens:'by token or agent', publicRun:'Public run', publicNote:'The canvas is readable without sign-in; reviewer names are hidden.', makePublic:'Make public', makePrivate:'Make private', publicHint:'Only for scenarios without personal data', publicConfirm:'Make this run public? Anyone with the link can read it without signing in. Use it only for scenarios without personal data.', openInApp:'Open in Kaloko', signInLink:'Sign in', replay:'Replay', rpStep:'Step', rpNext:'Next', rpPrev:'Back', rpEnd:'End of the walkthrough.', rpRestart:'Start over', rpChoose:'Which way?', rpOr:'or', rpKeys:'← → step · digits pick a branch · Esc closes', rpDecision:'Decision', rpBranch:'branch', rpAltOnly:'other branch',
1120
1125
  compare:'Compare', cmpWith:'Compared with', cmpStop:'Stop comparing', cmpOff:'nothing', cmpBaseline:'baseline', cmpPrev:'previous', cmpTitle:'Differences from', cmpNone:'Statuses, verdicts and evidence are the same.', pxChanged:'pixels changed', cmpSide:'Side by side', cmpDiff:'Diff', cmpLoadErr:'Could not load the compared run.', pxUnavail:'Pixel diff works in the service only (same-origin images).', was:'was', thisRun:'this run', cmpNoCap:'no capture in the compared run', cmpAdded:'new steps', cmpRemoved:'steps that disappeared', cmpHint:'Ctrl/⌘ + click a step to open the comparison',
1121
- reviewTitle:'Sign-off and comments', approve:'Approve', reject:'Reject', comment:'Comment', notePh:'What should change, or a note · @name notifies a colleague', approved:'approved', rejected:'rejected', commented:'comment', resolved:'resolved', resolve:'Resolve', openFeedback:'Open feedback', noFeedback:'No open feedback.', saved:'Saved', noteRequired:'Write what should change.', expires:'Expires' }
1126
+ reviewTitle:'Approval and comments', approve:'Approve', reject:'Return', comment:'Comment', notePh:'What should change, or a note · @name notifies a colleague', approved:'approved', rejected:'returned', commented:'comment', resolved:'resolved', resolve:'Resolve', openFeedback:'Open feedback', noFeedback:'No open feedback.', saved:'Saved', noteRequired:'Write what should change.', expires:'Expires' }
1122
1127
  };
1123
1128
  // Dev mode (#42)
1124
1129
  Object.assign(I18N.cs, { dim_token_mode:'Režim tokenů', dim_step_state:'Stav kroku', stateFallback:'Tento krok stav {s} nemá, ukazuje se výchozí.', dim_browser:'Prohlížeč', dim_color_scheme:'Barevné schéma', dim_reduced_motion:'Pohyb', dim_forced_colors:'Vynucené barvy', dim_display_mode:'Režim zobrazení', frame:'Rámeček', frameTitle:'Zobrazit snímek v rámečku zařízení', safeAreas:'Bezpečné zóny', safeTitle:'Ukázat výřez, indikátor domovské obrazovky a konec první obrazovky', safeArea:'Bezpečná zóna: obsah sem nepatří', firstScreenEnd:'konec první obrazovky', altCompare:'Vedle:', pickSearch:'Hledat…', pickNone:'Nic neodpovídá.', pickFailing:'Kolik hodnot má selhané kroky', keyLoc:'L vybere jazyk, [ a ] přepnou na předchozí a další' });
@@ -1147,14 +1152,14 @@ function switchLang (l) {
1147
1152
  location.reload();
1148
1153
  }
1149
1154
  // recordings and traces of steps (batch D, #12, #23)
1150
- Object.assign(I18N.cs, { videoMode:'Video', recExpired:(d) => `Záznam vypršel ${d}; snímky zůstávají.`, recUntil:(d) => `záznam do ${d}`, recFailed:'Záznam neúspěšného pokusu', recChapter:'Úsek videa celého flow', recStart:'Začátek kroku', recCapture:'Snímek', recLoadFail:'Video se nepodařilo načíst.', recMarks:'Značky ve videu', recHint:'Video kroku: od předchozí obrazovky po tento snímek', traceTitle:'Průběh kroku', traceLead:'Akce, požadavky a zprávy konzole od začátku kroku do snímku. Klikněte na řádek a video skočí na ten okamžik.', traceActions:'Akce', traceRequests:'Požadavky', traceFailed:'neúspěšné', traceErrors:'Chyby v konzoli', traceLoading:'Načítám průběh…', traceFail:'Průběh se nepodařilo načíst.', traceEmpty:'Během kroku se nic nezaznamenalo.', trAct:'akce', trNet:'síť', trCon:'konzole', walkthrough:'Video průchodu', walkthroughTitle:'Video celého průchodu s titulky kroků (stáhnout)', flowVideo:'Video flow', chapters:'Kapitoly' });
1151
- Object.assign(I18N.en, { videoMode:'Video', recExpired:(d) => `The recording expired on ${d}; the screenshots stay.`, recUntil:(d) => `recording until ${d}`, recFailed:'Recording of the failed attempt', recChapter:'Part of the flow recording', recStart:'Start of the step', recCapture:'Capture', recLoadFail:'The video could not be loaded.', recMarks:'Markers in the video', recHint:'The step on video: from the previous screen to this capture', traceTitle:'What happened in the step', traceLead:'Actions, requests and console messages from the start of the step to its capture. Click a row and the video jumps there.', traceActions:'Actions', traceRequests:'Requests', traceFailed:'failed', traceErrors:'Console errors', traceLoading:'Loading the trace…', traceFail:'The trace could not be loaded.', traceEmpty:'Nothing was recorded during the step.', trAct:'action', trNet:'network', trCon:'console', walkthrough:'Walkthrough video', walkthroughTitle:'Video of the whole walkthrough with step titles (download)', flowVideo:'Flow video', chapters:'Chapters' });
1155
+ Object.assign(I18N.cs, { videoMode:'Video', recExpired:(d) => `Záznam vypršel ${d}; snímky zůstávají.`, recUntil:(d) => `záznam do ${d}`, recFailed:'Záznam neúspěšného pokusu', recChapter:'Úsek videa celého scénáře', recStart:'Začátek kroku', recCapture:'Snímek', recLoadFail:'Video se nepodařilo načíst.', recMarks:'Značky ve videu', recHint:'Video kroku: od předchozí obrazovky po tento snímek', traceTitle:'Průběh kroku', traceLead:'Akce, požadavky a zprávy konzole od začátku kroku do snímku. Klikněte na řádek a video skočí na ten okamžik.', traceActions:'Akce', traceRequests:'Požadavky', traceFailed:'neúspěšné', traceErrors:'Chyby v konzoli', traceLoading:'Načítám průběh…', traceFail:'Průběh se nepodařilo načíst.', traceEmpty:'Během kroku se nic nezaznamenalo.', trAct:'akce', trNet:'síť', trCon:'konzole', walkthrough:'Video průchodu', walkthroughTitle:'Video celého průchodu s titulky kroků (stáhnout)', flowVideo:'Video scénáře', chapters:'Kapitoly' });
1156
+ Object.assign(I18N.en, { videoMode:'Video', recExpired:(d) => `The recording expired on ${d}; the screenshots stay.`, recUntil:(d) => `recording until ${d}`, recFailed:'Recording of the failed attempt', recChapter:'Part of the scenario recording', recStart:'Start of the step', recCapture:'Capture', recLoadFail:'The video could not be loaded.', recMarks:'Markers in the video', recHint:'The step on video: from the previous screen to this capture', traceTitle:'What happened in the step', traceLead:'Actions, requests and console messages from the start of the step to its capture. Click a row and the video jumps there.', traceActions:'Actions', traceRequests:'Requests', traceFailed:'failed', traceErrors:'Console errors', traceLoading:'Loading the trace…', traceFail:'The trace could not be loaded.', traceEmpty:'Nothing was recorded during the step.', trAct:'action', trNet:'network', trCon:'console', walkthrough:'Walkthrough video', walkthroughTitle:'Video of the whole walkthrough with step titles (download)', flowVideo:'Scenario video', chapters:'Chapters' });
1152
1157
  const T = (k) => I18N[UI][k] ?? I18N.en[k] ?? k;
1153
1158
  Object.assign(I18N.cs, { skipMain:'Přeskočit na obsah', sideLabel:'Běh a kroky', rsideLabel:'Shrnutí', rpSideLabel:'Krok', rvSideLabel:'Rozhodnutí o kroku' });
1154
1159
  Object.assign(I18N.en, { skipMain:'Skip to content', sideLabel:'Run and steps', rsideLabel:'Summary', rpSideLabel:'Step', rvSideLabel:'Decision on the step' });
1155
1160
  // the scenario and run explorer on a phone
1156
- Object.assign(I18N.cs, { close:'Zavřít', allScenarios:'Všechny scénáře', brShown:'Zobrazený', cmpRun:'Porovnat s během z', cmpActive:'Porovnává se', otherRuns:'Jiné běhy scénáře', listPages:'Stránky' });
1157
- Object.assign(I18N.en, { close:'Close', allScenarios:'All scenarios', brShown:'Shown', cmpRun:'Compare with the run of', cmpActive:'Comparing', otherRuns:'Other runs of this scenario', listPages:'Pages' });
1161
+ Object.assign(I18N.cs, { close:'Zavřít', prevLabel:'Předchozí', nextLabel:'Další', allScenarios:'Všechny scénáře', brShown:'Zobrazený', cmpRun:'Porovnat s během z', cmpActive:'Porovnává se', otherRuns:'Jiné běhy scénáře', listPages:'Stránky' });
1162
+ Object.assign(I18N.en, { close:'Close', prevLabel:'Previous', nextLabel:'Next', allScenarios:'All scenarios', brShown:'Shown', cmpRun:'Compare with the run of', cmpActive:'Comparing', otherRuns:'Other runs of this scenario', listPages:'Pages' });
1158
1163
  // "n pages" with the right plural (cs: 1 stránka, 2–4 stránky, 5+ stránek; en: 1 page, n pages)
1159
1164
  const pagesN = (n) => { n = Number(n) || 0; return UI === 'cs' ? (n === 1 ? 'stránka' : n >= 2 && n <= 4 ? 'stránky' : 'stránek') : (n === 1 ? 'page' : 'pages') };
1160
1165
  // "n runs" (cs: 1 běh, 2–4 běhy, 5+ běhů)
@@ -2235,7 +2240,7 @@ async function openStack (id, opts = {}) {
2235
2240
  const el = st.inline ? $('ri') : $('si');
2236
2241
  el.innerHTML = `<div class="si__box">
2237
2242
  <div class="si__head"><span class="kind" style="background:var(--ink);color:var(--panel)">${ICON.stack}</span><div class="si__title">${st.inline ? `<div class="ri-crumbs"><button id="siUp">${esc(RUN.scenario.title)}</button><span>›</span><b id="siTitle">${esc(loc(n.title))}</b></div>` : `<h3 id="siTitle">${esc(loc(n.title))}</h3>`}<div class="note">${esc(n.stack.count)} ${pagesN(n.stack.count)}${n.stack.source ? ` · ${esc(n.stack.source)}` : ''}${n.stack.semantic_sample != null && crits.some((c) => c.type === 'semantic') ? ` · ${T('semanticSample')} ${esc(Math.min(n.stack.semantic_sample, n.stack.count))}` : ''}</div></div>
2238
- <div class="seg" id="siView"></div><button class="btn" id="siCsv">${T('downloadCsv')}</button>${st.inline ? '' : `<button class="icon-btn" id="siClose" aria-label="Close">${ICON.close}</button>`}</div>
2243
+ <div class="seg" id="siView"></div><button class="btn" id="siCsv">${T('downloadCsv')}</button>${st.inline ? '' : `<button class="icon-btn" id="siClose" aria-label="${T('close')}">${ICON.close}</button>`}</div>
2239
2244
  <div class="si__tools">
2240
2245
  ${['all', 'fail', 'partial', 'pass', 'skip'].filter((k) => k === 'all' || sum[k]).map((k) => `<button class="si-chip" data-st="${k}" aria-pressed="${st.status === k}">${k === 'all' ? T('allWord') : `${MARK[k]} ${T(k)}`} ${k === 'all' ? esc(n.stack.count) : esc(sum[k] || 0)}</button>`).join('')}
2241
2246
  <select id="siCrit" aria-label="${esc(T('criterionFilter'))}"><option value="">${T('anyCriterion')}</option>${crits.map((c) => `<option value="${esc(c.id)}" ${st.crit === c.id ? 'selected' : ''}>${esc(c.id)} ${esc(c.text)}${c.bad ? ` (${c.bad})` : ''}</option>`).join('')}</select>
@@ -2580,7 +2585,7 @@ function renderPresence () {
2580
2585
  const now = Date.now(); for (const [k, x] of PR.agents) if (x.until < now) PR.agents.delete(k);
2581
2586
  const presentBtn = PR.ws && peers.length ? `<button class="pvis" data-present aria-pressed="${PR.presenting}">${esc(PR.presenting ? T('stopPresent') : T('present'))}</button>` : '';
2582
2587
  const visBtn = `<button class="pvis" data-vis title="${esc(presenceOn() ? T('visOn') : T('visOff'))}">${presenceOn() ? '●' : '○'} ${esc(presenceOn() ? T('visOn') : T('visOff'))}</button>`;
2583
- const menu = document.body.classList.contains('wide') ? presentBtn + visBtn : `<button class="pmenu-btn" data-pmenu aria-expanded="${!!PR.menu}" title="${esc(presenceOn() ? T('visOn') : T('visOff'))}">${presenceOn() ? '●' : '○'}</button>${PR.menu ? `<div class="menu pmenu">${PR.ws && peers.length ? `<button data-present aria-pressed="${PR.presenting}">${esc(PR.presenting ? T('stopPresent') : T('present'))}</button>` : ''}<button data-vis>${presenceOn() ? '●' : '○'} ${esc(presenceOn() ? T('visOn') : T('visOff'))}</button></div>` : ''}`;
2588
+ const menu = document.body.classList.contains('wide') ? presentBtn + visBtn : `<button class="pmenu-btn" data-pmenu aria-expanded="${!!PR.menu}" title="${esc(presenceOn() ? T('visOn') : T('visOff'))}" aria-label="${esc(presenceOn() ? T('visOn') : T('visOff'))}">${presenceOn() ? '●' : '○'}</button>${PR.menu ? `<div class="menu pmenu">${PR.ws && peers.length ? `<button data-present aria-pressed="${PR.presenting}">${esc(PR.presenting ? T('stopPresent') : T('present'))}</button>` : ''}<button data-vis>${presenceOn() ? '●' : '○'} ${esc(presenceOn() ? T('visOn') : T('visOff'))}</button></div>` : ''}`;
2584
2589
  // a phone shows two faces at most (one when the bar is tight), a computer six; the rest is a count
2585
2590
  const tight = !!$('presence').closest('.bar')?.classList.contains('is-tight'); const max = phoneBar() ? (tight ? 1 : 2) : tight ? 2 : 6;
2586
2591
  const faces = [...[...PR.agents.values()].map((x) => `<span class="pav pav--agent" style="--c:#151514" title="${esc(x.name)} ${esc(T('viaAgent'))} ${esc(x.email)}${x.step ? ` · ${esc(x.step)}` : ''}">AI</span>`), ...peers.map((p) => `<button class="pav" style="--c:${peerColor(p.email)}" data-follow="${esc(p.id)}" aria-pressed="${PR.follow === p.id}" title="${esc(p.email)} · ${esc(T('followHint'))}">${esc(initials(p.name || p.email))}</button>`)];
@@ -3271,7 +3276,7 @@ function openLb (id, vpName, opts = {}) {
3271
3276
  <div class="lb__grab" id="lbGrab" role="button" tabindex="0" aria-controls="lbSide" aria-expanded="${(S.lbSheet || 'peek') !== 'peek'}" aria-label="${esc(T('sheetHandle'))}"></div>
3272
3277
  <div class="lb__head">
3273
3278
  <div class="lb__row"><span class="kind">${ICON[n.kind]}</span><span class="lb__meta"><span class="badge st-${s}">${MARK[s]} ${T(s)}</span><span class="note">${n.item ? T('pageWord') : T('step')} ${i + 1} ${T('of')} ${list.length}</span>${n.item ? `<button class="btn" id="lbBack">${ICON.stack} ${T('backToIndex')}</button>` : ''}</span>
3274
- <span class="lb__nav">${APP() ? `<button class="btn c-jump" id="lbComments" title="${esc(T('commentsTitle'))} (C)">${ICON.comment}${openCount('step', n.id) ? ` ${openCount('step', n.id)}` : ''}</button>` : ''}<button class="btn" id="lbLink" title="${T('copyLink')}">${ICON.external} <span class="lb__linkTxt">${T('copyLink')}</span></button><button class="icon-btn" id="lbPrev" aria-label="Previous" ${i === 0 ? 'disabled' : ''}>${ICON.prev}</button><button class="icon-btn" id="lbNext" aria-label="Next" ${i === list.length - 1 ? 'disabled' : ''}>${ICON.next}</button><button class="icon-btn" id="lbClose" aria-label="Close">${ICON.close}</button></span></div>
3279
+ <span class="lb__nav">${APP() ? `<button class="btn c-jump" id="lbComments" title="${esc(T('commentsTitle'))} (C)">${ICON.comment}${openCount('step', n.id) ? ` ${openCount('step', n.id)}` : ''}</button>` : ''}<button class="btn" id="lbLink" title="${T('copyLink')}">${ICON.external} <span class="lb__linkTxt">${T('copyLink')}</span></button><button class="icon-btn" id="lbPrev" aria-label="${T('prevLabel')}" ${i === 0 ? 'disabled' : ''}>${ICON.prev}</button><button class="icon-btn" id="lbNext" aria-label="${T('nextLabel')}" ${i === list.length - 1 ? 'disabled' : ''}>${ICON.next}</button><button class="icon-btn" id="lbClose" aria-label="${T('close')}">${ICON.close}</button></span></div>
3275
3280
  <h3 id="lbTitle">${esc(loc(n.title))}</h3>
3276
3281
  <div class="node__path">${n.session ? sessChip(n.session) + ' · ' : ''}${cap?.http_status >= 300 ? `<span class="http ${cap.http_status >= 500 ? 'http--5' : ''}" title="HTTP ${esc(cap.http_status)}">${esc(cap.http_status)}</span>` : ''}${esc(cap?.live ? cap.live.entry : cap?.url || loc(n.path))}</div>
3277
3282
  ${sameAsCmp(n) ? `<p class="note">${T('unchangedSince')} ${esc(cmpLabel())}</p>` : ''}${cap?.live?.stale_capture ? `<p class="note">${T('staleCapture')}</p>` : ''}${isStateFallback(n, v.name) ? `<p class="note">${esc(T('stateFallback').replace('{s}', dimLabel(stateOfKey(v.name))))}</p>` : ''}${stepNoteHtml(n)}
@@ -3474,7 +3479,7 @@ function renderFocusBar (lb, n, v, cap, list, i) {
3474
3479
  const modes = [...(liveSrc(cap) ? [['live', T('liveMode')]] : []), ...(viewSrc(cap) ? [['view', T('htmlView')]] : []), ...(recOf(n, cap, v) ? [['video', T('videoMode')]] : [])];
3475
3480
  if (modes.length && !S.cmp) modes.push(['shot', T('shotMode')]); else modes.length = 0;
3476
3481
  const vps = n.item ? viewports().filter((x) => n.captures?.[S.loc]?.[x.name]) : viewports();
3477
- lb.insertAdjacentHTML('beforeend', `<div class="lb__fbar" id="lbFbar" role="toolbar" aria-label="${esc(T('focusBar'))}"><button class="icon-btn" id="lbFexit" aria-label="${esc(T('focusExit'))}" title="${esc(T('focusExit'))} (Esc)">${ICON.close}</button><button class="icon-btn" id="lbFprev" aria-label="Previous" ${i <= 0 ? 'disabled' : ''}>${ICON.prev}</button><span class="lb__fpos" title="${esc(loc(n.title))}">${i + 1} / ${list.length}</span><button class="icon-btn" id="lbFnext" aria-label="Next" ${i >= list.length - 1 ? 'disabled' : ''}>${ICON.next}</button>${vps.length > 1 ? '<div class="seg" id="lbFvp"></div>' : ''}${modes.length ? '<div class="seg" id="lbFmode"></div>' : ''}</div><button class="lb__fshow" id="lbFshow" aria-label="${esc(T('focusShow'))}" title="${esc(T('focusShow'))}">${ICON.more}</button>`);
3482
+ lb.insertAdjacentHTML('beforeend', `<div class="lb__fbar" id="lbFbar" role="toolbar" aria-label="${esc(T('focusBar'))}"><button class="icon-btn" id="lbFexit" aria-label="${esc(T('focusExit'))}" title="${esc(T('focusExit'))} (Esc)">${ICON.close}</button><button class="icon-btn" id="lbFprev" aria-label="${T('prevLabel')}" ${i <= 0 ? 'disabled' : ''}>${ICON.prev}</button><span class="lb__fpos" title="${esc(loc(n.title))}">${i + 1} / ${list.length}</span><button class="icon-btn" id="lbFnext" aria-label="${T('nextLabel')}" ${i >= list.length - 1 ? 'disabled' : ''}>${ICON.next}</button>${vps.length > 1 ? '<div class="seg" id="lbFvp"></div>' : ''}${modes.length ? '<div class="seg" id="lbFmode"></div>' : ''}</div><button class="lb__fshow" id="lbFshow" aria-label="${esc(T('focusShow'))}" title="${esc(T('focusShow'))}">${ICON.more}</button>`);
3478
3483
  $('lbFexit').onclick = () => setFocus(false);
3479
3484
  $('lbFprev').onclick = () => $('lbPrev')?.click();
3480
3485
  $('lbFnext').onclick = () => $('lbNext')?.click();
@@ -3675,7 +3680,7 @@ function renderReplay () {
3675
3680
  const crit = n.kind === 'decision' ? '' : `<div class="sec"><h4>${T('criteria')}</h4><ul class="ac">${n.criteria.map((c) => { const r = c.by_locale?.[S.loc]?.[v.name] || {}; const st = VSTAT[r.verdict || 'not_reached']; return `<li class="st-${st}"><span class="mark">${MARK[st]}</span><span><span class="id">${esc(c.id)}</span>${esc(c.text)}${r.score != null ? ` <span class="id">${r.score.toFixed(2)}</span>` : ''}</span>${r.evidence || r.note ? `<code class="ev">${esc(r.evidence || r.note)}</code>` : ''}</li>`; }).join('') || `<li class="st-pass"><span class="mark">–</span><span>—</span></li>`}</ul></div>`;
3676
3681
  $('rp').innerHTML = `
3677
3682
  <div class="rp__top"><span class="brand">Kaloko <small>${T('replay')}</small></span><span class="note">${T('rpStep')} ${S.rp.path.length}</span><span class="crumbs" id="rpCrumbs">${crumbs}</span><span class="spacer"></span>
3678
- ${rpHasVideo(v) ? `<button class="btn" id="rpVid" aria-pressed="${!!S.rpVideo}" title="${esc(T('recHint'))}">▶ ${T('videoMode')}</button>` : ''}${walkthroughLink()}<div class="seg" id="rpLoc"></div><div class="seg" id="rpVp"></div><span class="note">${T('rpKeys')}</span><button class="icon-btn" id="rpClose" aria-label="Close">${ICON.close}</button></div>
3683
+ ${rpHasVideo(v) ? `<button class="btn" id="rpVid" aria-pressed="${!!S.rpVideo}" title="${esc(T('recHint'))}">▶ ${T('videoMode')}</button>` : ''}${walkthroughLink()}<div class="seg" id="rpLoc"></div><div class="seg" id="rpVp"></div><span class="note">${T('rpKeys')}</span><button class="icon-btn" id="rpClose" aria-label="${T('close')}">${ICON.close}</button></div>
3679
3684
  <div class="rp__body"><div class="rp__stage" id="rpStage">${stage}</div>
3680
3685
  <aside class="rp__side" aria-label="${esc(T('rpSideLabel'))}"><div class="lb__head"><div class="lb__row">${n.kind === 'decision' ? `<span class="diamond" style="width:14px;height:14px"></span>` : `<span class="kind">${ICON[n.kind]}</span><span class="badge st-${s}">${MARK[s]} ${T(s)}</span>`}<span class="note">${esc(RUN.scenario.title)}</span></div><h3>${esc(loc(n.title))}</h3>${n.kind !== 'decision' ? `<div class="node__path">${esc(cap?.url || loc(n.path) || '')}</div>` : ''}</div>
3681
3686
  ${n.purpose ? `<div class="sec"><h4>${T('purpose')}</h4><p class="purpose">${esc(n.purpose)}</p></div>` : ''}${crit}
@@ -4660,7 +4665,7 @@ function renderPt () {
4660
4665
  const note = !src ? T('protoShot') : APP() ? T('protoHint') : T('protoLocal');
4661
4666
  el.innerHTML = `<div class="pt__bar"><span class="pt__title" id="ptTitle">${esc(loc(n.title))}</span><span class="note">${i + 1} / ${list.length}</span><span class="spacer"></span><div class="seg" id="ptVp"></div>${RUN.scope.locales.length > 1 ? '<div class="seg" id="ptLoc"></div>' : ''}<button class="btn" id="ptDev" aria-pressed="${PT.device}">${T('protoDevice')}</button>${document.fullscreenEnabled ? `<button class="btn" id="ptFull" aria-pressed="${!!document.fullscreenElement}">${T('protoFull')}</button>` : ''}${src && APP() ? `<button class="btn" id="ptState" title="${esc(T('protoStateTitle'))}" ${pstateEmpty() ? 'disabled' : ''}>${T('protoState')}</button>` : ''}<button class="btn" id="ptLink">${ICON.external} ${T('copyLink')}</button><button class="icon-btn" id="ptClose" aria-label="${esc(T('protoExit'))}" title="${esc(T('protoExit'))} (Esc)">${ICON.close}</button></div>
4662
4667
  <div class="pt__stage" id="ptStage"><div class="pt__fit" id="ptFit"><div class="pt__dev pt__dev--${kind} ${PT.device ? '' : 'pt__dev--bare'}" id="ptDevBox"><div class="pt__screen" style="width:${v.w}px;height:${v.h}px">${screen}</div></div></div></div>
4663
- <div class="pt__bar pt__bar--foot"><button class="icon-btn" id="ptPrev" aria-label="Previous" ${i <= 0 ? 'disabled' : ''}>${ICON.prev}</button><span class="note">${esc(note)}</span><button class="icon-btn" id="ptNext" aria-label="Next" ${i >= list.length - 1 ? 'disabled' : ''}>${ICON.next}</button></div>`;
4668
+ <div class="pt__bar pt__bar--foot"><button class="icon-btn" id="ptPrev" aria-label="${T('prevLabel')}" ${i <= 0 ? 'disabled' : ''}>${ICON.prev}</button><span class="note">${esc(note)}</span><button class="icon-btn" id="ptNext" aria-label="${T('nextLabel')}" ${i >= list.length - 1 ? 'disabled' : ''}>${ICON.next}</button></div>`;
4664
4669
  el.hidden = false;
4665
4670
  seg($('ptVp'), viewports().map((x) => [x.name, vpLabel(x)]), v.name, (nv) => ptOpen(PT.id, nv, PT));
4666
4671
  if ($('ptLoc')) picker($('ptLoc'), { name: 'loc', title: T('locale'), values: localeValues(), current: S.loc, onPick: (l) => { S.loc = l; renderAll(); ptOpen(PT.id, PT.vp, PT); } });
@@ -5037,13 +5042,13 @@ function piiNote (cap) {
5037
5042
  Object.assign(I18N.cs, {
5038
5043
  issTitle: 'Issues', issNew: (k) => `Založit issue v ${k === 'Jira' ? 'Jiře' : 'Linearu'}`, issFormTitle: 'Nové issue', issTracker: 'Kam',
5039
5044
  issCriterion: 'Kritérium', issWholeStep: 'Celý krok', issName: 'Název', issNamePh: 'Prázdné: název se složí z kroku a kritéria', issNote: 'Poznámka pro vývoj',
5040
- issSend: 'Založit issue', issSending: 'Zakládám issue…', issSent: (k) => `Issue ${k} je založené`, issWhat: 'Do issue půjde snímek, kritérium, připomínky a odkaz zpět na krok. Až bude hotové, komentář u kroku se vyřeší.',
5045
+ issSend: 'Založit issue', issSending: 'Zakládám issue…', issSent: (k) => `Issue ${k} je založené`, issWhat: 'Do issue půjde snímek, kritérium, komentáře a odkaz zpět na krok. Až bude hotové, komentář u kroku se vyřeší.',
5041
5046
  iss_open: 'otevřené', iss_done: 'hotové', iss_canceled: 'zrušené', issBranch: 'Issue z větve', issCount: (n) => `${n} ${n === 1 ? 'otevřené issue' : n < 5 ? 'otevřená issues' : 'otevřených issues'}`
5042
5047
  })
5043
5048
  Object.assign(I18N.en, {
5044
5049
  issTitle: 'Issues', issNew: (k) => `Create a ${k} issue`, issFormTitle: 'New issue', issTracker: 'Where',
5045
5050
  issCriterion: 'Criterion', issWholeStep: 'The whole step', issName: 'Title', issNamePh: 'Empty: made from the step and the criterion', issNote: 'Note for development',
5046
- issSend: 'Create the issue', issSending: 'Creating the issue…', issSent: (k) => `Issue ${k} created`, issWhat: 'The issue gets the screenshot, the criterion, the notes and a link back to the step. When it is done, the comment on the step is resolved.',
5051
+ issSend: 'Create the issue', issSending: 'Creating the issue…', issSent: (k) => `Issue ${k} created`, issWhat: 'The issue gets the screenshot, the criterion, the comments and a link back to the step. When it is done, the comment on the step is resolved.',
5047
5052
  iss_open: 'open', iss_done: 'done', iss_canceled: 'cancelled', issBranch: 'Issue from the branch', issCount: (n) => `${n} open issue${n === 1 ? '' : 's'}`
5048
5053
  })
5049
5054
  var ISS = { data: null, form: null, busy: false }; // var: the footer may render before this line runs
@@ -5144,7 +5149,7 @@ Object.assign(I18N.cs, {
5144
5149
  rvDone: 'Všechny kroky jsou rozhodnuté', rvLeft: (n) => `Na rozhodnutí ${n === 1 ? 'čeká 1 krok' : n < 5 ? `čekají ${n} kroky` : `čeká ${n} kroků`}`,
5145
5150
  rvNothing: 'Nic nečeká na člověka: kroky prošly a od přijatého běhu se nezměnily.', rvSumApproved: 'schváleno', rvSumReturned: 'vráceno', rvSumClear: 'beze změny a bez připomínek',
5146
5151
  rvCarriedTitle: 'Převzatá schválení', rvCarriedLead: 'Tyto kroky vypadají stejně jako v přijatém běhu a jejich automatické kontroly prošly. U každého je, kdo ho schválil.',
5147
- rvShow: 'Zobrazit', rvVerdictLead: 'Převzatá schválení se za přijetí běhu nepočítají: běh jako celek přijímá člověk.',
5152
+ rvShow: 'Zobrazit', rvVerdictLead: 'Převzatá schválení se za přijetí běhu nepočítají: běh jako celek přijímá člověk.', rvVerdictAsk: 'Běh přijměte, když může ven; když ne, vraťte ho s poznámkou.', rvAcceptedLead: 'Běh je přijatý. Záznam o přijetí níže pošlete klientovi nebo si ho nechte pro audit.',
5148
5153
  rvReturnedHint: 'Vrácené kroky uvidí autor běhu v inboxu a agent v kaloko feedback.', rvCarriedShort: 'převzato', rvToEnd: 'Souhrn', rvHint: 'kontrola krok po kroku', rvQueue: 'Kroky ke kontrole', rvRefFailed: 'Přijatý běh se nepodařilo načíst.', rvComparedWith: 'Porovnáno s přijatým během'
5149
5154
  })
5150
5155
  Object.assign(I18N.en, {
@@ -5165,7 +5170,7 @@ Object.assign(I18N.en, {
5165
5170
  rvDone: 'Every step is decided', rvLeft: (n) => `${n} step${n === 1 ? '' : 's'} still wait${n === 1 ? 's' : ''} for a decision`,
5166
5171
  rvNothing: 'Nothing waits for a person: the steps passed and did not change since the accepted run.', rvSumApproved: 'approved', rvSumReturned: 'returned', rvSumClear: 'unchanged, nothing to look at',
5167
5172
  rvCarriedTitle: 'Carried-over approvals', rvCarriedLead: 'These steps look the same as in the accepted run and their automatic checks passed. Each says who approved it.',
5168
- rvShow: 'Show', rvVerdictLead: 'Carried approvals do not count as accepting the run: a person accepts the run as a whole.',
5173
+ rvShow: 'Show', rvVerdictLead: 'Carried approvals do not count as accepting the run: a person accepts the run as a whole.', rvVerdictAsk: 'Accept the run when it can go out; return it with a note when it cannot.', rvAcceptedLead: 'The run is accepted. The acceptance record below is what you send the client or keep for an audit.',
5169
5174
  rvReturnedHint: 'The author of the run sees returned steps in the inbox, the agent in kaloko feedback.', rvCarriedShort: 'carried over', rvToEnd: 'Summary', rvHint: 'review step by step', rvQueue: 'Steps to review', rvRefFailed: 'The accepted run could not be loaded.', rvComparedWith: 'Compared with the accepted run'
5170
5175
  })
5171
5176
  const RV = { open: false, data: null, idx: 0, view: null, vp: null, refBase: {}, ret: false, busy: false, extra: [], flash: null };
@@ -5216,8 +5221,10 @@ function rvButton () {
5216
5221
  const b = $('rvBtn'); if (!b) return;
5217
5222
  const n = RV.data?.counts?.pending || 0;
5218
5223
  b.title = `${T('rvBtnTitle')} (Q)`;
5224
+ // the word "Review" is hidden on narrower screens: the button's name must not shrink to the bare count
5225
+ b.setAttribute('aria-label', n ? `${T('rvTitle')}: ${T('rvLeft')(n)}` : T('rvTitle'));
5219
5226
  b.querySelector('.rv-count')?.remove();
5220
- if (n) b.insertAdjacentHTML('beforeend', `<b class="rv-count" aria-label="${esc(T('rvLeft')(n))}">${n}</b>`);
5227
+ if (n) b.insertAdjacentHTML('beforeend', `<b class="rv-count" aria-hidden="true">${n}</b>`);
5221
5228
  }
5222
5229
  /** The canvas card of a step whose approval carried over from the accepted run. */
5223
5230
  function rvNodeBadge (id) {
@@ -5288,6 +5295,7 @@ function rvDecisionHtml (it) {
5288
5295
  function rvRender () {
5289
5296
  const box = $('rv'); if (!box || !RV.data) return;
5290
5297
  const d = RV.data, items = rvItems(), total = d.items.length, c = d.counts || {};
5298
+ box.classList.toggle('rvm--ret', !!(RV.ret && items[RV.idx] && rvWritable()));
5291
5299
  const top = `<div class="rvm__top"><span class="brand">Kaloko <small>${esc(T('rvTitle'))}</small></span>
5292
5300
  <span class="rvm__prog" role="status" aria-live="polite"><b>${esc(c.decided || 0)}</b> ${esc(T('of'))} ${esc(total)} ${esc(T('rvDecided'))}</span><span class="rvm__bar" aria-hidden="true"><i style="width:${total ? Math.round((c.decided || 0) / total * 100) : 100}%"></i></span>
5293
5301
  ${c.carried ? `<span class="badge st-pass" title="${esc(T('rvCarriedLead'))}">✓ ${esc(c.carried)} ${esc(T('rvCarriedN'))}</span>` : ''}<span class="spacer"></span>
@@ -5298,7 +5306,7 @@ function rvRender () {
5298
5306
  const n = byId(it.step);
5299
5307
  if (!n) { RV.idx = Math.min(RV.idx + 1, items.length); return rvRender(); }
5300
5308
  const vps = viewports().filter((x) => n.captures?.[S.loc]?.[x.name]);
5301
- if (!RV.vp || !vps.some((x) => x.name === RV.vp)) RV.vp = (vps.find((x) => x.name === (S.vp === 'mobile' ? mVp() : dVp())?.name) || vps[0] || viewports()[0]).name;
5309
+ if (!RV.vp || !vps.some((x) => x.name === RV.vp)) RV.vp = (vps.find((x) => x.name === (S.vp === 'mobile' || (S.vp === 'both' && lbPhone()) ? mVp() : dVp())?.name) || vps[0] || viewports()[0]).name;
5302
5310
  const v = viewports().find((x) => x.name === RV.vp) || viewports()[0];
5303
5311
  const cap = n.captures?.[S.loc]?.[v.name];
5304
5312
  const ref = d.reference; const refPath = it.ref_captures?.[S.loc]?.[v.name] || null;
@@ -5342,9 +5350,9 @@ function rvEndHtml () {
5342
5350
  const list = items.length ? `<div class="sec"><h4>${esc(T('rvQueue'))}</h4><ul class="rvm__list">${items.map((it, k) => `<li><span class="badge st-${!it.decision ? 'skip' : it.decision.state === 'returned' ? 'fail' : 'pass'}">${!it.decision ? '○' : it.decision.state === 'returned' ? '↩' : '✓'}</span><button class="linkish" data-rv-go="${k}">${esc(loc(byId(it.step)?.title) || it.step)}</button><span class="note">${it.reasons.map((r) => esc(T('rvR_' + r))).join(', ')}</span></li>`).join('')}</ul></div>` : '';
5343
5351
  const carried = (d.carried || []).length ? `<div class="sec"><h4>${esc(T('rvCarriedTitle'))}</h4><p class="note">${esc(T('rvCarriedLead'))}</p><ul class="rvm__list">${d.carried.map((x) => `<li><span class="badge st-pass">✓</span><button class="linkish" data-rv-show="${esc(x.step)}">${esc(loc(byId(x.step)?.title) || x.step)}</button><span class="note">${esc((x.kind === 'run' ? T('rvCarriedRun') : T('rvCarriedStep'))(x.from_run, x.approved_by, rvDay(x.approved_at)))}</span>${rvWritable() ? `<button class="linkish" data-rv-revoke="${esc(x.step)}">${esc(T('rvRevoke'))}</button>` : ''}</li>`).join('')}</ul></div>` : '';
5344
5352
  const v = RUN.verdict || d.verdict || {};
5345
- const verdict = rvWritable() ? `<div class="sec verdict-sec"><h4 class="vhead"><span>${T('runVerdict')}</span><span class="badge st-${v.verdict === 'accepted' ? 'pass' : v.verdict === 'returned' ? 'fail' : 'skip'}">${T(v.verdict || 'pending')}</span></h4><p class="note">${esc(T('rvVerdictLead'))}${returned ? ` ${esc(T('rvReturnedHint'))}` : ''}</p>${v.approvers ? `<p class="note">${v.awaiting?.length ? `${T('awaiting')}: <b>${v.awaiting.map(esc).join(', ')}</b>` : T('approvedByAll')}</p>` : ''}
5353
+ const verdict = rvWritable() ? `<div class="sec verdict-sec"><h4 class="vhead"><span>${T('runVerdict')}</span><span class="badge st-${v.verdict === 'accepted' ? 'pass' : v.verdict === 'returned' ? 'fail' : 'skip'}">${T(v.verdict || 'pending')}</span></h4><p class="note">${esc(T(v.verdict === 'accepted' ? 'rvAcceptedLead' : c.carried ? 'rvVerdictLead' : 'rvVerdictAsk'))}${returned ? ` ${esc(T('rvReturnedHint'))}` : ''}</p>${v.approvers ? `<p class="note">${v.awaiting?.length ? `${T('awaiting')}: <b>${v.awaiting.map(esc).join(', ')}</b>` : T('approvedByAll')}</p>` : ''}
5346
5354
  <textarea id="rvVNote" rows="2" placeholder="${esc(T('verdictNotePh'))}" data-draft="${esc(draftKey('run', '', 'verdict'))}" style="width:100%;font:inherit;padding:8px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink);margin:0 0 8px">${esc(loadDraft(draftKey('run', '', 'verdict')))}</textarea>
5347
- <div class="share__row">${verdictButtons(v.verdict, 'data-rv-verdict', false)}</div></div>` : '';
5355
+ <div class="share__row">${verdictButtons(v.verdict, 'data-rv-verdict', false)}</div>${signoffHtml()}</div>` : '';
5348
5356
  return `<div class="rvm__end"><h3>${esc(head)}</h3><p class="note">${esc(sum)}</p>${d.reference ? `<p class="note">${esc(T('rvComparedWith'))}: <a class="pr" href="${runHref(d.reference.id)}">${fmtDate(d.reference.started_at)}${d.reference.accepted_by ? ` · ${esc(d.reference.accepted_by)}` : ''}</a></p>` : ''}${list}${carried}${verdict}</div>`;
5349
5357
  }
5350
5358
  function rvWire (it, n, v) {