xonecode 0.3.0 → 0.5.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/README.md +49 -7
- package/THIRD_PARTY_NOTICES.md +128 -0
- package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
- package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
- package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
- package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
- package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
- package/apps/web/dist/assets/html-CfGypltT.js +1 -0
- package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
- package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
- package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
- package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
- package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
- package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
- package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
- package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
- package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
- package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
- package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
- package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
- package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
- package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
- package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
- package/apps/web/dist/iconos/xonecode.png +0 -0
- package/apps/web/dist/index.html +20 -0
- package/dist/agent/agentesEnDisco.js +539 -0
- package/dist/agent/agentesEnDisco.js.map +1 -0
- package/dist/agent/arbolDeProyecto.js +245 -0
- package/dist/agent/arbolDeProyecto.js.map +1 -0
- package/dist/agent/artefactosEnDisco.js +139 -0
- package/dist/agent/artefactosEnDisco.js.map +1 -0
- package/dist/agent/aumentador.js +175 -0
- package/dist/agent/aumentador.js.map +1 -0
- package/dist/agent/authEnDisco.js +76 -1
- package/dist/agent/authEnDisco.js.map +1 -1
- package/dist/agent/busquedaRegex.js +92 -0
- package/dist/agent/busquedaRegex.js.map +1 -0
- package/dist/agent/catalogoModelos.js +345 -0
- package/dist/agent/catalogoModelos.js.map +1 -0
- package/dist/agent/checkpointer.js +127 -0
- package/dist/agent/checkpointer.js.map +1 -0
- package/dist/agent/cloudstudioClient.js +194 -0
- package/dist/agent/cloudstudioClient.js.map +1 -0
- package/dist/agent/cloudstudioMcp.js +722 -0
- package/dist/agent/cloudstudioMcp.js.map +1 -0
- package/dist/agent/configEnDisco.js +309 -11
- package/dist/agent/configEnDisco.js.map +1 -1
- package/dist/agent/consumoExterno.js +99 -0
- package/dist/agent/consumoExterno.js.map +1 -0
- package/dist/agent/descarga.js +149 -0
- package/dist/agent/descarga.js.map +1 -0
- package/dist/agent/diagnosticoDeTools.js +51 -0
- package/dist/agent/diagnosticoDeTools.js.map +1 -0
- package/dist/agent/dispositivosEnMaquina.js +596 -0
- package/dist/agent/dispositivosEnMaquina.js.map +1 -0
- package/dist/agent/escrituraDeCodex.js +134 -0
- package/dist/agent/escrituraDeCodex.js.map +1 -0
- package/dist/agent/escrituraDeOpencode.js +80 -0
- package/dist/agent/escrituraDeOpencode.js.map +1 -0
- package/dist/agent/escrituraExterna.js +742 -0
- package/dist/agent/escrituraExterna.js.map +1 -0
- package/dist/agent/gemini.js +81 -0
- package/dist/agent/gemini.js.map +1 -0
- package/dist/agent/git.js +83 -0
- package/dist/agent/git.js.map +1 -0
- package/dist/agent/gitSync.js +441 -0
- package/dist/agent/gitSync.js.map +1 -0
- package/dist/agent/hotswap.js +448 -0
- package/dist/agent/hotswap.js.map +1 -0
- package/dist/agent/instalacionEnMaquina.js +252 -0
- package/dist/agent/instalacionEnMaquina.js.map +1 -0
- package/dist/agent/instantanea.js +7 -10
- package/dist/agent/instantanea.js.map +1 -1
- package/dist/agent/interrupts.js +11 -2
- package/dist/agent/interrupts.js.map +1 -1
- package/dist/agent/juezDeTarea.js +345 -0
- package/dist/agent/juezDeTarea.js.map +1 -0
- package/dist/agent/lanzamientoEnMaquina.js +377 -0
- package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
- package/dist/agent/manifiesto.js +59 -0
- package/dist/agent/manifiesto.js.map +1 -0
- package/dist/agent/memoriaDeProyecto.js +60 -0
- package/dist/agent/memoriaDeProyecto.js.map +1 -0
- package/dist/agent/modelos.js +105 -17
- package/dist/agent/modelos.js.map +1 -1
- package/dist/agent/modelosDeMotor.js +204 -0
- package/dist/agent/modelosDeMotor.js.map +1 -0
- package/dist/agent/paqueteDelProyecto.js +181 -0
- package/dist/agent/paqueteDelProyecto.js.map +1 -0
- package/dist/agent/perfiles.js +92 -31
- package/dist/agent/perfiles.js.map +1 -1
- package/dist/agent/persona.js +40 -0
- package/dist/agent/persona.js.map +1 -0
- package/dist/agent/procesosEnMaquina.js +204 -0
- package/dist/agent/procesosEnMaquina.js.map +1 -0
- package/dist/agent/proyecto.js +311 -13
- package/dist/agent/proyecto.js.map +1 -1
- package/dist/agent/puente.js +63 -6
- package/dist/agent/puente.js.map +1 -1
- package/dist/agent/resumenDeContexto.js +30 -0
- package/dist/agent/resumenDeContexto.js.map +1 -0
- package/dist/agent/resumenDeTool.js +64 -18
- package/dist/agent/resumenDeTool.js.map +1 -1
- package/dist/agent/sesionGit.js +481 -0
- package/dist/agent/sesionGit.js.map +1 -0
- package/dist/agent/settingsEnDisco.js +189 -0
- package/dist/agent/settingsEnDisco.js.map +1 -0
- package/dist/agent/subagenteCodex.js +368 -0
- package/dist/agent/subagenteCodex.js.map +1 -0
- package/dist/agent/subagenteExterno.js +423 -0
- package/dist/agent/subagenteExterno.js.map +1 -0
- package/dist/agent/subagenteOpencode.js +389 -0
- package/dist/agent/subagenteOpencode.js.map +1 -0
- package/dist/agent/subida.js +202 -0
- package/dist/agent/subida.js.map +1 -0
- package/dist/agent/tareasEnDisco.js +466 -0
- package/dist/agent/tareasEnDisco.js.map +1 -0
- package/dist/agent/turnoReal.js +663 -50
- package/dist/agent/turnoReal.js.map +1 -1
- package/dist/agent/versionEnDisco.js +48 -0
- package/dist/agent/versionEnDisco.js.map +1 -0
- package/dist/agent/xoneAgent.js +290 -49
- package/dist/agent/xoneAgent.js.map +1 -1
- package/dist/agent/zip.js +52 -0
- package/dist/agent/zip.js.map +1 -0
- package/dist/cli/acuseDeModelo.js +30 -0
- package/dist/cli/acuseDeModelo.js.map +1 -0
- package/dist/cli/aprobar.js +55 -2
- package/dist/cli/aprobar.js.map +1 -1
- package/dist/cli/config.js +9 -13
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/consola.js +750 -19
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +740 -81
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/markdown.js +188 -2
- package/dist/cli/markdown.js.map +1 -1
- package/dist/cli/run.js +32 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stdio.js +46 -19
- package/dist/cli/stdio.js.map +1 -1
- package/dist/cli/tema.js +96 -8
- package/dist/cli/tema.js.map +1 -1
- package/dist/cli/tokens.js +29 -0
- package/dist/cli/tokens.js.map +1 -0
- package/dist/cli/tui/app.js +171 -0
- package/dist/cli/tui/app.js.map +1 -0
- package/dist/cli/tui/aprobarTui.js +80 -0
- package/dist/cli/tui/aprobarTui.js.map +1 -0
- package/dist/cli/tui/barra.js +37 -0
- package/dist/cli/tui/barra.js.map +1 -0
- package/dist/cli/tui/correrTui.js +478 -0
- package/dist/cli/tui/correrTui.js.map +1 -0
- package/dist/cli/tui/entrada.js +166 -0
- package/dist/cli/tui/entrada.js.map +1 -0
- package/dist/cli/tui/filas.js +24 -0
- package/dist/cli/tui/filas.js.map +1 -0
- package/dist/cli/tui/logo.js +49 -0
- package/dist/cli/tui/logo.js.map +1 -0
- package/dist/cli/tui/pielTui.js +14 -0
- package/dist/cli/tui/pielTui.js.map +1 -0
- package/dist/cli/tui/raton.js +126 -0
- package/dist/cli/tui/raton.js.map +1 -0
- package/dist/cli/tui/sidebar.js +86 -0
- package/dist/cli/tui/sidebar.js.map +1 -0
- package/dist/cli/tui/store.js +143 -0
- package/dist/cli/tui/store.js.map +1 -0
- package/dist/cli/tui/tarjeta.js +19 -0
- package/dist/cli/tui/tarjeta.js.map +1 -0
- package/dist/cli/tui/temaInk.js +114 -0
- package/dist/cli/tui/temaInk.js.map +1 -0
- package/dist/cli/tui/transcript.js +254 -0
- package/dist/cli/tui/transcript.js.map +1 -0
- package/dist/cli/wizardInicial.js +240 -0
- package/dist/cli/wizardInicial.js.map +1 -0
- package/dist/core/actos.js +113 -0
- package/dist/core/actos.js.map +1 -0
- package/dist/core/adjuntos.js +97 -0
- package/dist/core/adjuntos.js.map +1 -0
- package/dist/core/agentes.js +227 -0
- package/dist/core/agentes.js.map +1 -0
- package/dist/core/artefactos.js +149 -0
- package/dist/core/artefactos.js.map +1 -0
- package/dist/core/cloudstudio.js +7 -0
- package/dist/core/cloudstudio.js.map +1 -0
- package/dist/core/config.js +212 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/contextos.js +53 -1
- package/dist/core/contextos.js.map +1 -1
- package/dist/core/descargas.js +106 -0
- package/dist/core/descargas.js.map +1 -0
- package/dist/core/descriptoresDeApp.js +279 -0
- package/dist/core/descriptoresDeApp.js.map +1 -0
- package/dist/core/dispositivos.js +466 -0
- package/dist/core/dispositivos.js.map +1 -0
- package/dist/core/entrega.js +141 -0
- package/dist/core/entrega.js.map +1 -0
- package/dist/core/entrelazar.js +90 -0
- package/dist/core/entrelazar.js.map +1 -0
- package/dist/core/esqueleto.js +10 -0
- package/dist/core/esqueleto.js.map +1 -1
- package/dist/core/modelos.js +235 -3
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/notify.js +27 -9
- package/dist/core/notify.js.map +1 -1
- package/dist/core/planDeSubida.js +127 -0
- package/dist/core/planDeSubida.js.map +1 -0
- package/dist/core/ports.js +189 -1
- package/dist/core/ports.js.map +1 -1
- package/dist/core/puedeLanzarse.js +239 -0
- package/dist/core/puedeLanzarse.js.map +1 -0
- package/dist/core/rutaVirtual.js +42 -0
- package/dist/core/rutaVirtual.js.map +1 -0
- package/dist/core/settings.js +246 -0
- package/dist/core/settings.js.map +1 -0
- package/dist/core/tareas.js +230 -0
- package/dist/core/tareas.js.map +1 -0
- package/dist/core/textos.js +36 -0
- package/dist/core/textos.js.map +1 -0
- package/dist/core/turno.js +56 -8
- package/dist/core/turno.js.map +1 -1
- package/dist/core/version.js +31 -0
- package/dist/core/version.js.map +1 -0
- package/dist/vendor/tokenTracking.js +22 -1
- package/dist/vendor/tokenTracking.js.map +1 -1
- package/dist/web/servidor/arranque.js +3964 -0
- package/dist/web/servidor/arranque.js.map +1 -0
- package/dist/web/servidor/consolaDeTarea.js +161 -0
- package/dist/web/servidor/consolaDeTarea.js.map +1 -0
- package/dist/web/servidor/consolaWeb.js +389 -0
- package/dist/web/servidor/consolaWeb.js.map +1 -0
- package/dist/web/servidor/corredorDeTareas.js +1119 -0
- package/dist/web/servidor/corredorDeTareas.js.map +1 -0
- package/dist/web/servidor/pielWeb.js +269 -0
- package/dist/web/servidor/pielWeb.js.map +1 -0
- package/dist/web/servidor/servidor.js +257 -0
- package/dist/web/servidor/servidor.js.map +1 -0
- package/dist/web/servidor/sesiones.js +440 -0
- package/dist/web/servidor/sesiones.js.map +1 -0
- package/dist/web/servidor/transporte.js +169 -0
- package/dist/web/servidor/transporte.js.map +1 -0
- package/dist/web/servidor/vestibulo.js +1276 -0
- package/dist/web/servidor/vestibulo.js.map +1 -0
- package/package.json +30 -8
- package/skills/archify/LICENSE +22 -0
- package/skills/archify/SKILL.md +109 -0
- package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
- package/skills/archify/assets/template.html +14787 -0
- package/skills/archify/bin/archify.mjs +1990 -0
- package/skills/archify/bin/open-artifact.mjs +86 -0
- package/skills/archify/bin/preview.mjs +648 -0
- package/skills/archify/bin/visual-check.mjs +829 -0
- package/skills/archify/brand-marks/README.md +31 -0
- package/skills/archify/brand-marks/catalog.json +131 -0
- package/skills/archify/delta/architecture-delta.mjs +1214 -0
- package/skills/archify/examples/agent-run.lifecycle.json +71 -0
- package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
- package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
- package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
- package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
- package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
- package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
- package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
- package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
- package/skills/archify/examples/event-stream.dataflow.json +57 -0
- package/skills/archify/examples/incident-response.workflow.json +64 -0
- package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
- package/skills/archify/examples/product-analytics.dataflow.json +76 -0
- package/skills/archify/examples/production-deployment.architecture.json +71 -0
- package/skills/archify/examples/release-delivery.workflow.json +62 -0
- package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
- package/skills/archify/examples/web-app-rendered.html +14862 -0
- package/skills/archify/examples/web-app.architecture.json +46 -0
- package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
- package/skills/archify/migrations/workflow-v2.mjs +279 -0
- package/skills/archify/package.json +17 -0
- package/skills/archify/recipes/scenarios.mjs +391 -0
- package/skills/archify/references/authoring-contract.md +198 -0
- package/skills/archify/references/brand-marks.md +69 -0
- package/skills/archify/references/delivery-contract.md +116 -0
- package/skills/archify/references/viewer-runtime.md +45 -0
- package/skills/archify/renderers/architecture/grid.mjs +62 -0
- package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
- package/skills/archify/renderers/dataflow/README.md +104 -0
- package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- package/skills/archify/renderers/lifecycle/README.md +115 -0
- package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- package/skills/archify/renderers/sequence/README.md +114 -0
- package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
- package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
- package/skills/archify/renderers/shared/cli.mjs +218 -0
- package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
- package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
- package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
- package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
- package/skills/archify/renderers/shared/geometry.mjs +1423 -0
- package/skills/archify/renderers/shared/i18n.mjs +594 -0
- package/skills/archify/renderers/shared/layout-report.mjs +40 -0
- package/skills/archify/renderers/shared/legend.mjs +217 -0
- package/skills/archify/renderers/shared/output-path.mjs +321 -0
- package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
- package/skills/archify/renderers/shared/text-fit.mjs +49 -0
- package/skills/archify/renderers/shared/utils.mjs +232 -0
- package/skills/archify/renderers/shared/validator.mjs +86 -0
- package/skills/archify/renderers/workflow/README.md +223 -0
- package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
- package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- package/skills/archify/schemas/README.md +211 -0
- package/skills/archify/schemas/architecture.schema.json +176 -0
- package/skills/archify/schemas/common.schema.json +115 -0
- package/skills/archify/schemas/dataflow.schema.json +243 -0
- package/skills/archify/schemas/lifecycle.schema.json +266 -0
- package/skills/archify/schemas/sequence.schema.json +223 -0
- package/skills/archify/schemas/workflow.schema.json +428 -0
- package/skills/archify/scripts/check-render-output.mjs +835 -0
- package/skills/archify/scripts/check-update.mjs +1667 -0
- package/skills/archify/scripts/render-examples.mjs +26 -0
- package/skills/archify/scripts/update-contract.mjs +182 -0
- package/skills/archify/skill-release.json +10 -0
- package/skills/artifacts-builder/SKILL.md +120 -0
- package/skills/artifacts-builder/reference/diagramas.md +123 -0
- package/skills/artifacts-builder/reference/estilo.md +123 -0
- package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
- package/skills/artifacts-builder/reference/graficas.md +22 -0
- package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
- package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
- package/skills/xone-hotswap/SKILL.md +185 -0
- package/skills/xone-hotswap/references/comandos.md +706 -0
- package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Colapsa un HTML con ficheros locales a UN solo fichero autocontenido.
|
|
3
|
+
|
|
4
|
+
Mismo trabajo que `html-inline` de la skill original de Anthropic, pero sin
|
|
5
|
+
npm: el contenedor tiene python3 y node, no npm.
|
|
6
|
+
|
|
7
|
+
Los recursos absolutos (http/https, //) se dejan como estan — el CSP decide que
|
|
8
|
+
CDNs se permiten (ver `validate_artifact.py`, que es quien enforza esa lista
|
|
9
|
+
blanca de hosts); este script no repite esa lista, solo distingue local de
|
|
10
|
+
absoluto por el esquema de la URL.
|
|
11
|
+
|
|
12
|
+
Solo libreria estandar.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import base64
|
|
17
|
+
import mimetypes
|
|
18
|
+
import pathlib
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from urllib.parse import urlparse
|
|
22
|
+
|
|
23
|
+
# Un unico regex con alternancia: `re.sub` solo escanea el string ORIGINAL, no
|
|
24
|
+
# vuelve a mirar lo que el propio callback acaba de insertar. Con tres pases
|
|
25
|
+
# separados (link, luego script, luego img) el pase de imagenes podia colarse
|
|
26
|
+
# DENTRO de un <script> ya inlineado si el JS traia algo con pinta de
|
|
27
|
+
# `<img src="...">` en un string literal — corrupcion silenciosa del JS.
|
|
28
|
+
_RECURSO = re.compile(
|
|
29
|
+
r"""<link\b[^>]*\bhref\s*=\s*["'](?P<css>[^"']+)["'][^>]*>"""
|
|
30
|
+
r"""|<script\b[^>]*\bsrc\s*=\s*["'](?P<js>[^"']+)["'][^>]*>\s*</script>"""
|
|
31
|
+
r"""|(?P<imgpre><img\b[^>]*\bsrc\s*=\s*["'])(?P<img>[^"']+)(?P<imgpost>["'])""",
|
|
32
|
+
re.I,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _es_local(url: str) -> bool:
|
|
37
|
+
u = (url or "").strip()
|
|
38
|
+
if not u or u.startswith(("data:", "blob:", "#")):
|
|
39
|
+
return False
|
|
40
|
+
p = urlparse(u)
|
|
41
|
+
if p.scheme in ("http", "https") or u.startswith("//"):
|
|
42
|
+
return False
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _leer(base: pathlib.Path, url: str) -> bytes | None:
|
|
47
|
+
ruta = (base / url.lstrip("/")).resolve()
|
|
48
|
+
try:
|
|
49
|
+
# No salir del directorio del artifact.
|
|
50
|
+
ruta.relative_to(base.resolve())
|
|
51
|
+
except ValueError:
|
|
52
|
+
print(f"[bundle] fuera del directorio, se ignora: {url}", file=sys.stderr)
|
|
53
|
+
return None
|
|
54
|
+
if not ruta.is_file():
|
|
55
|
+
print(f"[bundle] no encontrado, se deja el tag: {url}", file=sys.stderr)
|
|
56
|
+
return None
|
|
57
|
+
return ruta.read_bytes()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def empaquetar(html: str, base: pathlib.Path) -> str:
|
|
61
|
+
"""Devuelve el HTML con todo recurso LOCAL embebido."""
|
|
62
|
+
base = pathlib.Path(base)
|
|
63
|
+
|
|
64
|
+
def _sub(m: re.Match[str]) -> str:
|
|
65
|
+
if m.group("css") is not None:
|
|
66
|
+
url = m.group("css")
|
|
67
|
+
if not _es_local(url) or "stylesheet" not in m.group(0).lower():
|
|
68
|
+
return m.group(0)
|
|
69
|
+
datos = _leer(base, url)
|
|
70
|
+
if datos is None:
|
|
71
|
+
return m.group(0)
|
|
72
|
+
return "<style>\n" + datos.decode("utf-8", "replace") + "\n</style>"
|
|
73
|
+
|
|
74
|
+
if m.group("js") is not None:
|
|
75
|
+
url = m.group("js")
|
|
76
|
+
if not _es_local(url):
|
|
77
|
+
return m.group(0)
|
|
78
|
+
datos = _leer(base, url)
|
|
79
|
+
if datos is None:
|
|
80
|
+
return m.group(0)
|
|
81
|
+
return "<script>\n" + datos.decode("utf-8", "replace") + "\n</script>"
|
|
82
|
+
|
|
83
|
+
# Imagen.
|
|
84
|
+
pre, url, post = m.group("imgpre"), m.group("img"), m.group("imgpost")
|
|
85
|
+
if not _es_local(url):
|
|
86
|
+
return m.group(0)
|
|
87
|
+
datos = _leer(base, url)
|
|
88
|
+
if datos is None:
|
|
89
|
+
return m.group(0)
|
|
90
|
+
mime = mimetypes.guess_type(url)[0] or "application/octet-stream"
|
|
91
|
+
return pre + f"data:{mime};base64," + base64.b64encode(datos).decode() + post
|
|
92
|
+
|
|
93
|
+
return _RECURSO.sub(_sub, html)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv: list[str]) -> int:
|
|
97
|
+
if len(argv) not in (2, 3):
|
|
98
|
+
print("uso: bundle_artifact.py <entrada.html> [salida.html]", file=sys.stderr)
|
|
99
|
+
return 2
|
|
100
|
+
entrada = pathlib.Path(argv[1])
|
|
101
|
+
salida = pathlib.Path(argv[2]) if len(argv) == 3 else entrada
|
|
102
|
+
resultado = empaquetar(entrada.read_text(encoding="utf-8"), entrada.parent)
|
|
103
|
+
salida.write_text(resultado, encoding="utf-8")
|
|
104
|
+
print(f"OK: {salida} ({len(resultado.encode('utf-8')) / 1024:.0f} KB)")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
raise SystemExit(main(sys.argv))
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Comprueba que un HTML cumple el contrato de artifact de NappAI.
|
|
3
|
+
|
|
4
|
+
Contrato (nivel 0): el artifact es autocontenido y NO habla con nadie. Cada
|
|
5
|
+
regla de aqui espeja una directiva del CSP con que se sirve; si se cambia una,
|
|
6
|
+
se cambia la otra.
|
|
7
|
+
|
|
8
|
+
OJO: esto es analisis estatico y se puede enganar (JS ofuscado). La garantia
|
|
9
|
+
real es el CSP de la respuesta, que lo impone el navegador. Esto da
|
|
10
|
+
realimentacion rapida al modelo, no seguridad.
|
|
11
|
+
|
|
12
|
+
Solo libreria estandar: corre en un contenedor pelado sin pip.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from html.parser import HTMLParser
|
|
19
|
+
from urllib.parse import urlparse
|
|
20
|
+
|
|
21
|
+
TOPE_BYTES = 5 * 1024 * 1024
|
|
22
|
+
|
|
23
|
+
CDN_SCRIPT = {"cdnjs.cloudflare.com", "cdn.jsdelivr.net"}
|
|
24
|
+
CDN_ESTILO = CDN_SCRIPT | {"fonts.googleapis.com"}
|
|
25
|
+
CDN_FUENTE = {"fonts.gstatic.com"}
|
|
26
|
+
|
|
27
|
+
_RED = [
|
|
28
|
+
(re.compile(r"\bfetch\s*\("), "fetch()"),
|
|
29
|
+
(re.compile(r"\bXMLHttpRequest\b"), "XMLHttpRequest"),
|
|
30
|
+
(re.compile(r"\bWebSocket\s*\("), "WebSocket"),
|
|
31
|
+
(re.compile(r"\bEventSource\s*\("), "EventSource"),
|
|
32
|
+
(re.compile(r"\bsendBeacon\s*\("), "navigator.sendBeacon()"),
|
|
33
|
+
(re.compile(r"\bimport\s*\("), "import() dinamico"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Almacenamiento del navegador. **Estas tres LANZAN en el sandbox del widget**, que pinta el
|
|
38
|
+
# artifact en un iframe con `sandbox="allow-scripts allow-downloads allow-modals"` y SIN
|
|
39
|
+
# `allow-same-origin`: el documento tiene origen opaco y merely LEER una de ellas da
|
|
40
|
+
#
|
|
41
|
+
# Failed to read the 'localStorage' property from 'Window':
|
|
42
|
+
# The document is sandboxed and lacks the 'allow-same-origin' flag.
|
|
43
|
+
#
|
|
44
|
+
# Es la MISMA clase que `fetch`: no es un gusto, es que el navegador lo rompe. Y es PEOR que
|
|
45
|
+
# `fetch`, porque el throw se lleva el bloque `<script>` entero y el fallo es MUDO --- medido en
|
|
46
|
+
# produccion el 2026-08-31: la pagina se veia perfecta y el diagrama era un rectangulo vacio.
|
|
47
|
+
_ALMACEN = ("localStorage", "sessionStorage", "indexedDB")
|
|
48
|
+
|
|
49
|
+
#: Ventanas alrededor de un acceso donde se busca su `try`/`catch`. Es analisis de texto y se
|
|
50
|
+
#: puede enganar --- como todo este fichero, que lo dice en su docstring--- pero caza el caso
|
|
51
|
+
#: comun: el acceso suelto, sin nada que lo envuelva.
|
|
52
|
+
_ANTES = 240
|
|
53
|
+
_DESPUES = 400
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _accesos_sin_guarda(html: str) -> list[str]:
|
|
57
|
+
"""Los accesos a almacenamiento que NO parecen envueltos en `try`/`catch`.
|
|
58
|
+
|
|
59
|
+
Para cada aparicion se mira hacia atras si hay un `try` cerca y hacia delante si hay un
|
|
60
|
+
`catch`. No es un parser de JS --- no lo hay en la libreria estandar y este fichero corre en
|
|
61
|
+
un contenedor pelado--- asi que se elige el error del lado seguro: se avisa del acceso
|
|
62
|
+
suelto, y un acceso envuelto de forma rara puede colarse. La alternativa (prohibirlo del
|
|
63
|
+
todo) obligaria a renunciar a recordar una pestana o un filtro por un fallo que tiene
|
|
64
|
+
arreglo de una linea.
|
|
65
|
+
"""
|
|
66
|
+
fuera: list[str] = []
|
|
67
|
+
for api in _ALMACEN:
|
|
68
|
+
for m in re.finditer(rf"\b{api}\b", html):
|
|
69
|
+
antes = html[max(0, m.start() - _ANTES) : m.start()]
|
|
70
|
+
despues = html[m.end() : m.end() + _DESPUES]
|
|
71
|
+
if "try" in antes and ("catch" in despues or "catch" in antes):
|
|
72
|
+
continue
|
|
73
|
+
fuera.append(api)
|
|
74
|
+
break # uno por API basta: el mensaje es el mismo
|
|
75
|
+
return fuera
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _host(url: str) -> str | None:
|
|
79
|
+
"""Host de una URL absoluta; None si es relativa, data: o blob:."""
|
|
80
|
+
url = (url or "").strip()
|
|
81
|
+
if not url or url.startswith(("data:", "blob:", "#")):
|
|
82
|
+
return None
|
|
83
|
+
p = urlparse(url)
|
|
84
|
+
if p.scheme in ("http", "https") or url.startswith("//"):
|
|
85
|
+
return (p.netloc or "").lower() or None
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class _Escaner(HTMLParser):
|
|
90
|
+
def __init__(self) -> None:
|
|
91
|
+
super().__init__(convert_charrefs=True)
|
|
92
|
+
self.problemas: list[str] = []
|
|
93
|
+
|
|
94
|
+
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
|
95
|
+
a = {k.lower(): (v or "") for k, v in attrs}
|
|
96
|
+
|
|
97
|
+
if tag == "base":
|
|
98
|
+
self.problemas.append("<base> no esta permitido: reescribe el destino de los enlaces.")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if tag == "script":
|
|
102
|
+
h = _host(a.get("src", ""))
|
|
103
|
+
if h and h not in CDN_SCRIPT:
|
|
104
|
+
self.problemas.append(
|
|
105
|
+
f"<script src> de '{h}': solo se permiten {sorted(CDN_SCRIPT)}."
|
|
106
|
+
)
|
|
107
|
+
elif tag == "link":
|
|
108
|
+
h = _host(a.get("href", ""))
|
|
109
|
+
if h and h not in CDN_ESTILO | CDN_FUENTE:
|
|
110
|
+
self.problemas.append(
|
|
111
|
+
f"<link href> de '{h}': solo se permiten {sorted(CDN_ESTILO | CDN_FUENTE)}."
|
|
112
|
+
)
|
|
113
|
+
elif tag in ("img", "video", "audio", "source", "embed"):
|
|
114
|
+
h = _host(a.get("src", ""))
|
|
115
|
+
if h:
|
|
116
|
+
self.problemas.append(
|
|
117
|
+
f"<{tag} src> de '{h}': usa data: o blob:. Un src externo saca datos "
|
|
118
|
+
"por la query string."
|
|
119
|
+
)
|
|
120
|
+
elif tag == "iframe":
|
|
121
|
+
if _host(a.get("src", "")):
|
|
122
|
+
self.problemas.append("<iframe src> externo no esta permitido.")
|
|
123
|
+
elif tag == "form":
|
|
124
|
+
if _host(a.get("action", "")):
|
|
125
|
+
self.problemas.append("<form action> externo no esta permitido.")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
#: Etiquetas de mermaid con PARENTESIS sin comillas. Es un error de SINTAXIS, no de estilo, y
|
|
129
|
+
#: rompe el diagrama entero: la pagina carga perfecta y en su sitio sale «Syntax error in text».
|
|
130
|
+
#:
|
|
131
|
+
#: **Medido contra mermaid 10.9.0 en un navegador, con `mermaid.parse` y su control**
|
|
132
|
+
#: (2026-09-01, sobre un panel real que fallo asi):
|
|
133
|
+
#:
|
|
134
|
+
#: | construccion | |
|
|
135
|
+
#: |---|---|
|
|
136
|
+
#: | `A[Uno] --> B[Dos]` | OK |
|
|
137
|
+
#: | `A[Mapeos (mapcol)]` | **ROMPE** |
|
|
138
|
+
#: | `A["Mapeos (mapcol)"]` | OK |
|
|
139
|
+
#: | `subgraph Vistas (contents)` | **ROMPE** |
|
|
140
|
+
#: | `subgraph S1 [Vistas (contents)]` | **ROMPE** |
|
|
141
|
+
#: | `subgraph "Vistas (contents)"` | OK |
|
|
142
|
+
#: | `A[Salida ##EXIT##]`, `A[LoginColl.xne]`, acentos, `:`, `@` | OK |
|
|
143
|
+
#:
|
|
144
|
+
#: **Las almohadillas estan INOCENTES** y es la segunda vez que se acusan en falso: `##EXIT##`
|
|
145
|
+
#: parsea sin problema. Lo que rompe son los parentesis, y en un proyecto XOne salen solos
|
|
146
|
+
#: (`(mapcol)`, `(contents)`), asi que esto no es un caso raro: es el caso normal.
|
|
147
|
+
#: Con que empieza un diagrama de mermaid, para reconocerlo dentro de una plantilla de JS.
|
|
148
|
+
_EMPIEZA_DIAGRAMA = re.compile(
|
|
149
|
+
r"\s*(?:%%\{.*?\}%%\s*)?"
|
|
150
|
+
r"(?:graph|flowchart|sequenceDiagram|stateDiagram(?:-v2)?|erDiagram|classDiagram|"
|
|
151
|
+
r"journey|gantt|pie|mindmap|timeline)\b",
|
|
152
|
+
re.S,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
_MERMAID_NODO = re.compile(r"\[([^\]\n]*)\]")
|
|
156
|
+
_MERMAID_SUBGRAPH = re.compile(r"^[ \t]*subgraph[ \t]+(.*)$", re.M)
|
|
157
|
+
#: Los tramos ENTRE COMILLAS, que es lo que hay que quitar antes de buscar un parentesis.
|
|
158
|
+
#:
|
|
159
|
+
#: **Y esto es el arreglo de un falso positivo, cazado a los diez minutos de escribir el
|
|
160
|
+
#: check**: la primera version exigia la comilla PEGADA al corchete (`\[(?!["\'])`), asi que
|
|
161
|
+
#: `Header ["Cabecera (frmTop)"]` --- con un espacio en medio, que es como lo escribe el modelo
|
|
162
|
+
#: --- se leia como sin comillas y el validador RECHAZABA un panel correcto. Un aviso que
|
|
163
|
+
#: desmiente trabajo bueno hace mas daño que uno que falta: el constructor habria reescrito
|
|
164
|
+
#: tres veces algo que ya estaba bien y habria acabado sin publicar.
|
|
165
|
+
_ENTRE_COMILLAS = re.compile(r"\"[^\"]*\"|'[^']*'")
|
|
166
|
+
|
|
167
|
+
#: Los delimitadores de FORMA en los bordes de una etiqueta. `A[("x")]` es un cilindro (base de
|
|
168
|
+
#: datos), `A([\"x\"])` un estadio, `A[[\"x\"]]` un subproceso: los parentesis y corchetes de los
|
|
169
|
+
#: extremos son ESTRUCTURA, no texto.
|
|
170
|
+
#:
|
|
171
|
+
#: **Segundo falso positivo de este check, y otra vez habria bloqueado un panel correcto**: sin
|
|
172
|
+
#: quitarlos, `A[("Coleccion Usuarios")]` se leia como una etiqueta con parentesis sin comillas
|
|
173
|
+
#: --- las comillas estaban DENTRO de la forma --- y el validador lo rechazaba. Los dos falsos
|
|
174
|
+
#: positivos han venido de lo mismo: mirar la cadena cruda en vez de la ETIQUETA.
|
|
175
|
+
_BORDES_DE_FORMA = re.compile(r"^[\[\(\{>/\\]+|[\]\)\}/\\]+$")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _solo_la_etiqueta(cruda: str) -> str:
|
|
179
|
+
"""El texto de la etiqueta: sin los delimitadores de forma y sin los tramos citados."""
|
|
180
|
+
return _ENTRE_COMILLAS.sub("", _BORDES_DE_FORMA.sub("", cruda.strip()))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _sin_lo_citado(texto: str) -> str:
|
|
184
|
+
"""El texto con los tramos entre comillas fuera. Lo que quede es lo NO citado."""
|
|
185
|
+
return _ENTRE_COMILLAS.sub("", texto)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _bloques_mermaid(html: str) -> list[str]:
|
|
189
|
+
"""El texto de cada diagrama mermaid del HTML.
|
|
190
|
+
|
|
191
|
+
Se buscan las DOS formas en que llega: el `<pre class="mermaid">`/`<div class="mermaid">`
|
|
192
|
+
del render automatico y la cadena que se le pasa a `mermaid.render`/`mermaid.parse` desde
|
|
193
|
+
JS. Con solo la primera, un panel que construye el diagrama en una constante de JavaScript
|
|
194
|
+
--- que es lo que hacen la mitad de los que se han visto --- pasaria sin mirar.
|
|
195
|
+
"""
|
|
196
|
+
fuera: list[str] = []
|
|
197
|
+
for m in re.finditer(
|
|
198
|
+
r"<(?:pre|div)[^>]*class=[\"'][^\"']*mermaid[^\"']*[\"'][^>]*>(.*?)</(?:pre|div)>",
|
|
199
|
+
html,
|
|
200
|
+
re.S | re.I,
|
|
201
|
+
):
|
|
202
|
+
fuera.append(m.group(1))
|
|
203
|
+
# **La segunda forma: el diagrama dentro de una PLANTILLA de JS** (`const src = \`graph TD
|
|
204
|
+
# …\`;`), que es como lo escribe la mitad de los paneles. Se acota a la plantilla completa
|
|
205
|
+
# —de comilla invertida a comilla invertida— y **eso es el arreglo del TERCER falso positivo
|
|
206
|
+
# de este check**: la version anterior buscaba `graph TD` y se llevaba hasta 20.000 chars
|
|
207
|
+
# «hasta la comilla o el `</script>`», asi que cuando el diagrama no estaba en una plantilla
|
|
208
|
+
# se tragaba el CSS de Tailwind que venia detras y marcaba `rounded-[var(--radius)]` como una
|
|
209
|
+
# etiqueta con parentesis sin comillas. Rechazaba un panel correcto.
|
|
210
|
+
#
|
|
211
|
+
# Los tres falsos positivos de este check han salido de lo mismo: **mirar texto que no es una
|
|
212
|
+
# etiqueta de mermaid.** Delimitar bien lo que se mira vale mas que afinar el patron.
|
|
213
|
+
for m in re.finditer(r"`([^`]*)`", html, re.S):
|
|
214
|
+
cuerpo = m.group(1)
|
|
215
|
+
if _EMPIEZA_DIAGRAMA.match(cuerpo):
|
|
216
|
+
fuera.append(cuerpo)
|
|
217
|
+
return fuera
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _mermaid_roto(html: str) -> list[str]:
|
|
221
|
+
"""Etiquetas de mermaid con un parentesis FUERA de comillas, con la culpable delante."""
|
|
222
|
+
problemas: list[str] = []
|
|
223
|
+
vistas: set[str] = set()
|
|
224
|
+
for bloque in _bloques_mermaid(html):
|
|
225
|
+
candidatas = [m.group(1) for m in _MERMAID_NODO.finditer(bloque)]
|
|
226
|
+
candidatas += [m.group(1) for m in _MERMAID_SUBGRAPH.finditer(bloque)]
|
|
227
|
+
for cruda in candidatas:
|
|
228
|
+
if "(" not in _solo_la_etiqueta(cruda):
|
|
229
|
+
continue
|
|
230
|
+
etiqueta = cruda.strip()[:80]
|
|
231
|
+
if etiqueta in vistas:
|
|
232
|
+
continue
|
|
233
|
+
vistas.add(etiqueta)
|
|
234
|
+
problemas.append(
|
|
235
|
+
f"Etiqueta de mermaid con parentesis SIN comillas: `{etiqueta}`. mermaid 10.9 "
|
|
236
|
+
f"no la parsea y el diagrama entero sale como «Syntax error in text» (medido "
|
|
237
|
+
f"con mermaid.parse). Pon el TEXTO entre comillas dobles: `[\"...\"]` en un "
|
|
238
|
+
f"nodo, `subgraph \"...\"` en un subgrafo."
|
|
239
|
+
)
|
|
240
|
+
return problemas
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def validar(html: str) -> tuple[bool, list[str]]:
|
|
244
|
+
"""Devuelve (ok, problemas). Cada problema dice que hacer, no solo que pasa."""
|
|
245
|
+
problemas: list[str] = []
|
|
246
|
+
|
|
247
|
+
tam = len(html.encode("utf-8"))
|
|
248
|
+
if tam > TOPE_BYTES:
|
|
249
|
+
problemas.append(
|
|
250
|
+
f"El artifact ocupa {tam / 1024 / 1024:.1f} MB y el tope son 5 MB. "
|
|
251
|
+
"Reduce los datos horneados o baja la resolucion de las imagenes."
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
for patron, nombre in _RED:
|
|
255
|
+
if patron.search(html):
|
|
256
|
+
problemas.append(
|
|
257
|
+
f"Usa {nombre}: el artifact es autocontenido y no puede hablar con el "
|
|
258
|
+
"servidor. Hornea los datos dentro del HTML."
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
problemas.extend(_mermaid_roto(html))
|
|
262
|
+
|
|
263
|
+
for api in _accesos_sin_guarda(html):
|
|
264
|
+
problemas.append(
|
|
265
|
+
f"Usa {api} sin try/catch: en el sandbox del visor LANZA "
|
|
266
|
+
f"(«The document is sandboxed and lacks the 'allow-same-origin' flag») y se lleva "
|
|
267
|
+
f"el bloque <script> entero, asi que la pagina se rompe EN SILENCIO. Para el tema "
|
|
268
|
+
f"basta matchMedia('(prefers-color-scheme: dark)'). Si necesitas guardar algo, "
|
|
269
|
+
f"envuelve cada acceso: try {{ {api}.getItem(k) }} catch {{}}."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
escaner = _Escaner()
|
|
273
|
+
try:
|
|
274
|
+
escaner.feed(html)
|
|
275
|
+
except AssertionError as e: # lo unico que HTMLParser levanta con markup patologico
|
|
276
|
+
problemas.append(f"El HTML no se puede parsear: {e}")
|
|
277
|
+
problemas.extend(escaner.problemas)
|
|
278
|
+
|
|
279
|
+
return (not problemas), problemas
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def avisos(html: str) -> list[str]:
|
|
283
|
+
"""Calidad, no rotura: se publica igual y se le dice.
|
|
284
|
+
|
|
285
|
+
**La distincion es la que hace que el contrato signifique algo.** Si un `alt` que falta
|
|
286
|
+
bloqueara la publicacion igual que una pagina que no arranca, el modelo aprenderia a leer la
|
|
287
|
+
lista entera como ruido. Aqui van las cosas que hacen el panel peor sin hacerlo imposible.
|
|
288
|
+
"""
|
|
289
|
+
fuera: list[str] = []
|
|
290
|
+
bajo = html.lower()
|
|
291
|
+
|
|
292
|
+
if not re.search(r"<!doctype\s+html", html, flags=re.IGNORECASE):
|
|
293
|
+
fuera.append("falta el doctype de HTML5: el navegador entra en modo quirks.")
|
|
294
|
+
if not re.search(r"<html[^>]*\slang=", html, flags=re.IGNORECASE):
|
|
295
|
+
fuera.append('falta `lang` en <html>: los lectores de pantalla no saben en que idioma leer.')
|
|
296
|
+
if "name=\"viewport\"" not in bajo and "name='viewport'" not in bajo:
|
|
297
|
+
fuera.append("falta el meta viewport: en movil se vera reducido al 25%.")
|
|
298
|
+
if not re.search(r"<title>\s*\S", html, flags=re.IGNORECASE):
|
|
299
|
+
fuera.append("falta un <title> con contenido: es el nombre del panel en la pestana.")
|
|
300
|
+
|
|
301
|
+
sin_alt = [
|
|
302
|
+
m.group(0)
|
|
303
|
+
for m in re.finditer(r"<img\b[^>]*>", html, flags=re.IGNORECASE)
|
|
304
|
+
if not re.search(r"\salt=", m.group(0), flags=re.IGNORECASE)
|
|
305
|
+
]
|
|
306
|
+
if sin_alt:
|
|
307
|
+
fuera.append(f"{len(sin_alt)} imagen(es) sin `alt`.")
|
|
308
|
+
|
|
309
|
+
botones_mudos = len(
|
|
310
|
+
[m for m in re.finditer(r"<button\b[^>]*>\s*</button>", html, flags=re.IGNORECASE)]
|
|
311
|
+
)
|
|
312
|
+
if botones_mudos:
|
|
313
|
+
fuera.append(
|
|
314
|
+
f"{botones_mudos} boton(es) sin texto ni etiqueta accesible: usa texto o aria-label."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
ids = re.findall(r"\sid=[\"']([^\"']+)[\"']", html)
|
|
318
|
+
repetidos = sorted({i for i in ids if ids.count(i) > 1})
|
|
319
|
+
if repetidos:
|
|
320
|
+
fuera.append(f"ids repetidos: {', '.join(repetidos[:5])}.")
|
|
321
|
+
|
|
322
|
+
interactivo = "<button" in bajo or "<a " in bajo
|
|
323
|
+
if interactivo and not re.search(r":focus-visible|:focus\b", html, flags=re.IGNORECASE):
|
|
324
|
+
fuera.append("ningun estilo de foco visible: con teclado no se ve donde estas.")
|
|
325
|
+
|
|
326
|
+
return fuera
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def main(argv: list[str]) -> int:
|
|
330
|
+
if len(argv) != 2:
|
|
331
|
+
print("uso: validate_artifact.py <fichero.html>", file=sys.stderr)
|
|
332
|
+
return 2
|
|
333
|
+
with open(argv[1], encoding="utf-8") as fh:
|
|
334
|
+
ok, problemas = validar(fh.read())
|
|
335
|
+
if ok:
|
|
336
|
+
print("OK: el artifact cumple el contrato.")
|
|
337
|
+
return 0
|
|
338
|
+
print("El artifact NO cumple el contrato:")
|
|
339
|
+
for p in problemas:
|
|
340
|
+
print(f" - {p}")
|
|
341
|
+
return 1
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
if __name__ == "__main__":
|
|
345
|
+
raise SystemExit(main(sys.argv))
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xone-hotswap
|
|
3
|
+
description: Probar una app XOne en un dispositivo o emulador LOCAL —Android o iOS— a través del servidor hotswap que la propia app host levanta en el dispositivo. Usar al desplegar una app en un móvil o simulador, al reiniciarla para que aplique cambios, al capturar la pantalla o el árbol de controles, al pulsar o rellenar controles desde fuera, al leer el log o consultar la base de datos del dispositivo, y al diagnosticar por qué el dispositivo no responde en el puerto 8443. Cubre las dos plataformas y dice, comando por comando, cuál de las dos lo tiene.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Probar una app XOne en un dispositivo local (hotswap)
|
|
7
|
+
|
|
8
|
+
El interlocutor es la **app host ya instalada** en el dispositivo. No se compila ni se instala
|
|
9
|
+
un paquete por iteración: se le suben los ficheros del proyecto, se reinicia y se mira.
|
|
10
|
+
|
|
11
|
+
| | Android | iOS |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| App host | **XoneStudio** (`com.xone.android.framework`, o `com.xone.android.developer.framework` en el flavor de Play Store) | **XOneStudioSwift** (`es.xone.studioapp.swift`) |
|
|
14
|
+
| Solo existe en | builds *debuggable* | el runtime en **modo desarrollo** (`XoneAppDeveloment`); en una app de producción no existe |
|
|
15
|
+
|
|
16
|
+
El servidor vive **dentro del proceso de la app**: si la app no está viva, el puerto no
|
|
17
|
+
responde. Habla dos protocolos por el mismo puerto: **WebSocket** para los comandos
|
|
18
|
+
(`{"command": "..."}` → `{"result": ..., "status": ...}`) y **HTTP(S)** para lo demás.
|
|
19
|
+
|
|
20
|
+
## Lo que hay que saber antes de tocar nada
|
|
21
|
+
|
|
22
|
+
- **HTTPS con certificado autofirmado** en las dos plataformas. Todas las URLs son `https://`;
|
|
23
|
+
con `curl` hace falta `-k`. Solo escucha en `http://` si alguien desactivó la conexión segura.
|
|
24
|
+
- **El puerto 8443 es el POR DEFECTO, no una garantía.** En Android, si está ocupado —otra APK
|
|
25
|
+
del framework instalada— el servidor coge el siguiente libre, y el real se ve en la pantalla
|
|
26
|
+
del servidor hotswap (pestaña Información).
|
|
27
|
+
- **Subir NO aplica.** El proceso tiene cargado en memoria lo de antes: hay que reiniciar la app.
|
|
28
|
+
- **La base de datos va cifrada con SQLCipher.** Subir un `.db` en claro termina en
|
|
29
|
+
`database disk image is malformed (code 11)`.
|
|
30
|
+
- **En iOS, `POST /command` sirve solo los comandos de automatización** — los de los bloques
|
|
31
|
+
**Ver la pantalla** y **Tocar**. Los comandos legados (`launchApplication`, `setAttribute`,
|
|
32
|
+
`listAppFiles`…) son **solo WebSocket**: sus manejadores responden escribiendo en el socket, que
|
|
33
|
+
en una petición HTTP no existe, así que por HTTP contestan `Unknown command '<x>'` aunque el
|
|
34
|
+
comando exista. Cuál es cuál, comando a comando: la columna **Canal (iOS)** de la
|
|
35
|
+
[matriz](references/comandos.md#matriz-de-disponibilidad).
|
|
36
|
+
|
|
37
|
+
## Cómo llegar, según la plataforma
|
|
38
|
+
|
|
39
|
+
**Android** — por `adb forward`, y entonces la IP es `127.0.0.1` (el localhost del PC, que adb
|
|
40
|
+
tuneliza al dispositivo). Con varios dispositivos, `adb -s <serial>`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
adb devices -l
|
|
44
|
+
adb shell am start -n com.xone.android.framework/com.xone.android.hotswap.activities.SetupActivity
|
|
45
|
+
adb forward tcp:8443 tcp:8443 # reaplicar tras cada reconexión del cable
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**iOS** — **no hay `adb` ni túnel**. El simulador comparte la pila de red del Mac, así que el
|
|
49
|
+
servidor es alcanzable directamente; en dispositivo físico se llega por la IP de la LAN que
|
|
50
|
+
muestra la propia app:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
xcrun simctl list devices booted
|
|
54
|
+
xcrun simctl launch <UDID> es.xone.studioapp.swift # arrancar el host levanta el servidor
|
|
55
|
+
curl -sk -X POST https://localhost:8443/command -d '{"command":"getAllElements"}'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Detalle de despliegue, relanzado y endpoints de fichero: [conexión y despliegue](references/conexion-y-despliegue.md).
|
|
59
|
+
|
|
60
|
+
## Qué hay en cada plataforma, de un vistazo
|
|
61
|
+
|
|
62
|
+
| Bloque | Android | iOS |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| Ver la pantalla (`getAllElements`, `getCurrentScreen`, `getScreenshot`, `getText`, `getUiAttribute`, `getRows`, `isVisible`, `isEnabled`, `waitForElement`) | sí | **sí**, con desviaciones (abajo) |
|
|
65
|
+
| Tocar (`click`, `tap`, `doubleTap`, `longPress`, `openSelector`, `fill`, `clear`, `scroll`, `pressKey`) | sí | **sí**, con desviaciones (abajo) |
|
|
66
|
+
| Ciclo de app (`launchApplication`, `exitApplication`, `directdownload`) | sí | `launchApplication` y `directdownload` sí; `exitApplication` no |
|
|
67
|
+
| Atributos y colecciones (`setAttribute`, `deleteAttribute`, `loadCollection`) | sí | sí |
|
|
68
|
+
| CSS, `replaceProperty`, `refresh`, `relayout`, `loadIncludeFile` | sí | **no** |
|
|
69
|
+
| Ficheros (`/file_upload`, `/file_download`, `uploadFile`, `getFileChecksum`) | sí | **no** (`listappfiles` sí) |
|
|
70
|
+
| Scripts y SQL (`runScript`, `runSql`, `getDatabaseTableNames`, `queryDatabaseTable`) | sí | **no** |
|
|
71
|
+
| Logs (`getLog`, `*DeviceLogging`, `setLog*`) | sí | **no** |
|
|
72
|
+
| Mock de hardware (`setMockData`, `clearMockData`) | sí | **no** |
|
|
73
|
+
| Edición en vivo (`enable/disableLiveEditionMode`) | sí | **no** |
|
|
74
|
+
| Depurador JS (`debug`) | sí | **no** (JavaScriptCore no tiene API pública de depuración) |
|
|
75
|
+
|
|
76
|
+
**Lo que en 5.0.2.2dev NO existe, medido el 16-sep-2026** (flavor standalone, Android, **con una app
|
|
77
|
+
ya cargada y viva**): `getCurrentScreen` y `status`/`state` contestan `Unknown command`, aunque esta
|
|
78
|
+
tabla los dé por disponibles. Para ver la pantalla, lo que responde es `getAllElements` —el árbol de
|
|
79
|
+
controles— y `getScreenshot` —el PNG en base64—. Y **el nombre de un comando es EXACTO**: no hay
|
|
80
|
+
alias insensible a mayúsculas. `launchApplication`, `setAttribute` y `listAppFiles` existen; sus
|
|
81
|
+
variantes en minúscula contestan `Unknown command`, que es como se descubrió.
|
|
82
|
+
|
|
83
|
+
Comando por comando, con su contrato y sus desviaciones: [comandos](references/comandos.md).
|
|
84
|
+
|
|
85
|
+
Los doce comandos que apuntan por nombre (`getText`, `isVisible`, `isEnabled`, `waitForElement`,
|
|
86
|
+
`click`, `tap`, `doubleTap`, `longPress`, `openSelector`, `fill`, `clear`, `scroll`) aceptan
|
|
87
|
+
además **`row` / `content` / `where`** para apuntar a una **fila**: por índice, por content, o por
|
|
88
|
+
**lo que contiene** (`{"ID":"2938733"}`). Solo existen las filas **pintadas**; el índice es el de
|
|
89
|
+
la colección y **no se renumera con el scroll**; y `waitForElement` con `row` espera a que la
|
|
90
|
+
fila **esté pintada**, no a que la colección tenga datos. Para leer las filas que NO están a la
|
|
91
|
+
vista, `getRows` — que lee el modelo, no las vistas.
|
|
92
|
+
|
|
93
|
+
## Lo que iOS no puede hacer IGUAL, y por qué
|
|
94
|
+
|
|
95
|
+
No son carencias de implementación: son límites y consecuencias de la plataforma, y el canal las
|
|
96
|
+
responde con un motivo en vez de fingir. **Declarar lo que iOS no puede hacer igual vale más que
|
|
97
|
+
fingir paridad**: con la diferencia delante se puede trabajar; con una promesa falsa, no.
|
|
98
|
+
|
|
99
|
+
- **No hay inyección de toques ni de teclas.** `click` invoca el manejador del control (como el
|
|
100
|
+
`performClick()` de Android), así que **`click` y `tap` colapsan** en el mismo efecto, y
|
|
101
|
+
`doubleTap`/`longPress` **siempre se niegan**: el runtime iOS no tiene eventos XONE de doble
|
|
102
|
+
toque ni de pulsación larga en props/frames. `pressKey` solo traduce `4`/`back` y `66`/`enter`
|
|
103
|
+
(con `key` simbólico: `back`, `enter` y `done`).
|
|
104
|
+
Por eso `has no click handler` va **sin** la coletilla `use 'tap' to send a real touch` de
|
|
105
|
+
Android: mandaría a repetir la llamada que acaba de fallar.
|
|
106
|
+
- **No hay codificación WebP.** `getScreenshot` devuelve JPEG al 50% (o PNG si se pide) y lo
|
|
107
|
+
declara en `imageFormat`.
|
|
108
|
+
- **La captura no lo captura todo**: quedan fuera la cámara en vivo, las capas Metal/GL, las
|
|
109
|
+
teselas de mapa y la barra de estado del sistema (otro proceso).
|
|
110
|
+
- **Los memos multilínea y los campos de fecha/hora no los escribe ESTE CANAL**, y lo dicen así:
|
|
111
|
+
`Element 'X' is editable, but this channel cannot write a multiline|date/time field`. El usuario
|
|
112
|
+
sí puede editarlos — no es un control equivocado, es un límite del canal.
|
|
113
|
+
- **Los dos volcados NO son compatibles atributo a atributo.** iOS emite **8** atributos y Android
|
|
114
|
+
**19**, con solo **5 comunes** (`index`, `class`, `text`, `enabled`, `bounds`). iOS no tiene
|
|
115
|
+
`resource-id` —con el que Android nombra las piezas de un diálogo— ni `content-desc` ni los
|
|
116
|
+
booleanos (`checkable`, `clickable`, `scrollable`…), y tiene tres propios (`accessibility-id`,
|
|
117
|
+
`label`, `visible`). En iOS el canónico es **`uikit`**; `uiautomator` se acepta como **alias**
|
|
118
|
+
para que una llamada multiplataforma no falle, pero **devuelve datos con forma de iOS**.
|
|
119
|
+
- **La búsqueda mira primero lo que está ENCIMA** (diálogos, toasts) y la pantalla al final.
|
|
120
|
+
Alcanzar un control tapado **nombrándolo** sigue permitido; lo que cambió es el orden. Los
|
|
121
|
+
niveles **anteriores** de navegación **no se buscan**: un `not found` sobre un control de la
|
|
122
|
+
pantalla que dejaste atrás significa que todavía no has navegado.
|
|
123
|
+
|
|
124
|
+
Las **once divergencias con Android**, con su porqué:
|
|
125
|
+
[comandos → Divergencias](references/comandos.md#divergencias-con-android).
|
|
126
|
+
|
|
127
|
+
## Dos avisos sobre el host iOS que te vas a encontrar
|
|
128
|
+
|
|
129
|
+
Ninguno es del hotswap; los dos te van a costar una sesión si no los sabes.
|
|
130
|
+
|
|
131
|
+
- **`MyAllXOne` crashea al arrancar** en el host (GoogleMaps). Para probar **filas**, el fixture
|
|
132
|
+
usable es **`FontIconsApp`**.
|
|
133
|
+
- **El host se cae al abrir una lista de menú**: le faltan los `xone_img_*.png`, y
|
|
134
|
+
`CollectionViewController`/`SortView` meten `UIImage` nil en arrays. Es un defecto **ajeno** al
|
|
135
|
+
hotswap. Si el puerto deja de responder justo después de abrir un menú, es esto.
|
|
136
|
+
|
|
137
|
+
## Cómo se prueba, y qué cuenta como evidencia
|
|
138
|
+
|
|
139
|
+
1. **Reproduce el estado**: lanza la app y espera al control que marca la pantalla lista
|
|
140
|
+
(`waitForElement`), nunca a un temporizador fijo.
|
|
141
|
+
2. **Pregunta dónde estás** (`getCurrentScreen`, **iOS**) tras cada navegación: dice la pantalla,
|
|
142
|
+
su colección, qué diálogos hay encima y cuál se lleva los toques. Es una respuesta corta, y
|
|
143
|
+
evita interpretar un volcado para averiguar lo mismo.
|
|
144
|
+
3. **Mira el árbol antes de tocar** (`getAllElements`). El formato `xone` da los controles con su
|
|
145
|
+
nombre XOne, que es por el que se pulsa; el otro formato da el volcado de la jerarquía de
|
|
146
|
+
vistas, útil para distinguir "ese control no existe" de "existe pero el motor no lo indexa".
|
|
147
|
+
Para el contenido de una **lista**, `getRows`: el volcado solo trae las filas pintadas.
|
|
148
|
+
4. **Actúa por NOMBRE de control** (`click`, `tap`, `fill`, `scroll`), no por coordenadas: las
|
|
149
|
+
coordenadas cambian con el tamaño de pantalla y no dicen qué se intentaba pulsar.
|
|
150
|
+
5. **Comprueba con un dato, no con una impresión**: `getText`, `isVisible`, `isEnabled`, y el
|
|
151
|
+
propio `getAllElements` antes y después. Una captura sirve para **enseñar** el fallo, no para
|
|
152
|
+
afirmar que algo funciona — nadie puede releerla.
|
|
153
|
+
6. **Si algo falla, trae el log** (Android) o la respuesta literal del comando (las dos
|
|
154
|
+
plataformas) y la línea concreta. «No funciona» no es un hallazgo.
|
|
155
|
+
|
|
156
|
+
## Reglas que evitan diagnósticos falsos
|
|
157
|
+
|
|
158
|
+
- **No inventes controles.** Si `getAllElements` no lo lista, no existe en esa pantalla: dilo en
|
|
159
|
+
vez de probar nombres a ver si suena alguno.
|
|
160
|
+
- **Un `result: false` es el resultado**, no un error del que recuperarse en silencio. Su `status`
|
|
161
|
+
dice exactamente qué pasó. En iOS, un control **ausente** ya no se confunde con uno
|
|
162
|
+
**intocable**, y cada mensaje manda a un sitio distinto: `not found` (corrige el nombre) ·
|
|
163
|
+
`is not enabled` (rellena antes la pantalla) · `has editing disabled` (desbloquéalo en la app).
|
|
164
|
+
El catálogo entero, con qué arregla cada uno:
|
|
165
|
+
[comandos → El catálogo de errores](references/comandos.md#el-catálogo-de-errores-desdoblado-ios).
|
|
166
|
+
- **Para saber si un campo se puede escribir, mira `editDisabled`, no `enabled`.** `enabled`
|
|
167
|
+
devuelve lo mismo en un campo bloqueado y en uno que no se escribe nunca; `editDisabled:true`
|
|
168
|
+
(que solo se emite cuando es cierto) es el único que los separa. Y su **ausencia no promete**
|
|
169
|
+
que el `fill` funcione: un memo o un campo de fecha salen sin él y este canal tampoco los
|
|
170
|
+
escribe.
|
|
171
|
+
- **Distingue «la app no está viva» de «la prueba falla».** Conexión rechazada en el puerto es lo
|
|
172
|
+
primero. En iOS hay además una distinción propia: `"App is not running"` significa que no hay
|
|
173
|
+
ninguna app XONE lanzada, mientras que `"Screen '<clase>' is not readable by the 'xone' format"`
|
|
174
|
+
significa que sí la hay, pero su pantalla es de un camino de render que ese formato no lee.
|
|
175
|
+
- **Después de subir, reinicia.** Un resultado medido sin reiniciar está midiendo la versión
|
|
176
|
+
anterior, y es el error más caro de todos porque no da ningún síntoma.
|
|
177
|
+
- **No dejes el dispositivo peor de como estaba**: `clearMockData` tras usar datos simulados
|
|
178
|
+
(Android), y `disableDeviceLogging` / `setLogDisabled` tras acabar.
|
|
179
|
+
|
|
180
|
+
## Referencias
|
|
181
|
+
|
|
182
|
+
| Qué | Dónde |
|
|
183
|
+
|---|---|
|
|
184
|
+
| Los comandos, uno a uno: entrada, salida, errores y qué plataforma lo tiene | [references/comandos.md](references/comandos.md) |
|
|
185
|
+
| Llegar al dispositivo, desplegar una app, relanzarla, y los endpoints de fichero | [references/conexion-y-despliegue.md](references/conexion-y-despliegue.md) |
|