specrails-desktop 2.34.0 → 2.35.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.
Files changed (209) hide show
  1. package/client/dist/assets/ActivityFeedPage-LCBJ5WiD.js +1 -0
  2. package/client/dist/assets/AgentBrowserCapture-B-xQpyZr.js +1 -0
  3. package/client/dist/assets/AgentModeAnalyticsPane-BI1Vcf3T.js +2 -0
  4. package/client/dist/assets/AgentModeCodePane-BTRwCl9h.js +2 -0
  5. package/client/dist/assets/AgentModeJobsPane-VzeBJLqP.js +1 -0
  6. package/client/dist/assets/AgentsPage-CJj1K24O.js +87 -0
  7. package/client/dist/assets/AnalyticsPage-CQpV5_fX.js +1 -0
  8. package/client/dist/assets/CodePage-9-tFh0jB.js +2 -0
  9. package/client/dist/assets/DesktopAnalyticsPage-BcZ0_O_C.js +1 -0
  10. package/client/dist/assets/DocsDialog-BPAzDICW.js +11 -0
  11. package/client/dist/assets/DocsPage-Dqo4Hn6G.js +11 -0
  12. package/client/dist/assets/IntegrationsPage-Bhy58lnQ.js +1 -0
  13. package/client/dist/assets/InteractiveJobComposer-D36fZWai.js +19 -0
  14. package/client/dist/assets/JobDetailModal-BxsktbkQ.js +1 -0
  15. package/client/dist/assets/JobDetailPage-WWyCy6Sp.js +1 -0
  16. package/client/dist/assets/JobsPage-CEbM7uxU.js +1 -0
  17. package/client/dist/assets/{LoopBuilderPage-WeiofeL6.js → LoopBuilderPage-CYsSN8io.js} +2 -2
  18. package/client/dist/assets/LoopPreviewModal-BJhgX5wY.js +1 -0
  19. package/client/dist/assets/LoopsPage-DB4OqMot.js +1 -0
  20. package/client/dist/assets/MinimizedChatsContext-CcjBHMHd.js +1 -0
  21. package/client/dist/assets/PluginsPage-CR8K4nM_.js +2 -0
  22. package/client/dist/assets/{ProjectSettingsDialog-BkCXBQWk.js → ProjectSettingsDialog-C45-qhF4.js} +1 -1
  23. package/client/dist/assets/ReviewPacketPage-Cz_QQeC3.js +1 -0
  24. package/client/dist/assets/TemplatePreviewModal-wFBd2vQP.js +1 -0
  25. package/client/dist/assets/TicketDetailModal-BvZoyDAL.js +14 -0
  26. package/client/dist/assets/{Trans-CTqw6rr9.js → Trans-DXZ-BLXb.js} +1 -1
  27. package/client/dist/assets/agent-BQgKv_2F.js +1 -0
  28. package/client/dist/assets/agent-C1NRDJqL.js +1 -0
  29. package/client/dist/assets/agent-CLNleiy6.js +1 -0
  30. package/client/dist/assets/agent-CmceArkQ.js +1 -0
  31. package/client/dist/assets/agent-D3UxQ3SR.js +1 -0
  32. package/client/dist/assets/agent-DB1JeE64.js +1 -0
  33. package/client/dist/assets/agent-DXkHcQ-D.js +1 -0
  34. package/client/dist/assets/agent-DjJJJ2M8.js +1 -0
  35. package/client/dist/assets/ban-E4EaZe3a.js +1 -0
  36. package/client/dist/assets/bot-DzuuuC5R.js +1 -0
  37. package/client/dist/assets/{browser-DxP-alBK.js → browser-BgqHDMXP.js} +1 -1
  38. package/client/dist/assets/{browser-CyR1Kp3m.js → browser-CsKJtrpv.js} +1 -1
  39. package/client/dist/assets/browser-Cu58EpZU.js +1 -0
  40. package/client/dist/assets/{browser-rRCwi7Y7.js → browser-CvXOJm-j.js} +1 -1
  41. package/client/dist/assets/{browser-CvgDySd9.js → browser-DOrFh1c7.js} +1 -1
  42. package/client/dist/assets/{browser-DwZljcRh.js → browser-DQv_Jo7x.js} +1 -1
  43. package/client/dist/assets/{browser-C25DkQiO.js → browser-LPgxvJu2.js} +1 -1
  44. package/client/dist/assets/{browser-CiJ7-L32.js → browser-eL-3MIEI.js} +1 -1
  45. package/client/dist/assets/button-CJqFnUOL.js +1 -0
  46. package/client/dist/assets/{chunk-KS7C4IRE-Cx5sqopX.js → chunk-KS7C4IRE-DEKu087k.js} +1 -1
  47. package/client/dist/assets/{commands-DyUZQ1FI.js → commands-6tYQih5m.js} +1 -1
  48. package/client/dist/assets/{commands-B9RZO4qS.js → commands-BK2QinXN.js} +1 -1
  49. package/client/dist/assets/{commands-Bbxu8kLU.js → commands-C2Nca76h.js} +1 -1
  50. package/client/dist/assets/{commands-CDU3qv68.js → commands-CKdLihNX.js} +1 -1
  51. package/client/dist/assets/{commands-DTQBF_q5.js → commands-CYIBTAlc.js} +1 -1
  52. package/client/dist/assets/{commands-hOtKuOXc.js → commands-CjUtYJIM.js} +1 -1
  53. package/client/dist/assets/{commands-BEwbAjtD.js → commands-Cq_u_8y9.js} +1 -1
  54. package/client/dist/assets/{commands-CzezgtQj.js → commands-D_ZHYQug.js} +1 -1
  55. package/client/dist/assets/core-B3mKBIh5.js +1 -0
  56. package/client/dist/assets/dashboard-B9GhC5ku.js +1 -0
  57. package/client/dist/assets/dashboard-C8WOEiPu.js +1 -0
  58. package/client/dist/assets/dashboard-CBnv6zOI.js +1 -0
  59. package/client/dist/assets/dashboard-CaP0fU87.js +1 -0
  60. package/client/dist/assets/dashboard-D4ZZ4x84.js +1 -0
  61. package/client/dist/assets/dashboard-DmuTQBX8.js +1 -0
  62. package/client/dist/assets/dashboard-DynLYj3d.js +1 -0
  63. package/client/dist/assets/dashboard-zj4gGFJ1.js +1 -0
  64. package/client/dist/assets/dialog-CdWCb02j.js +45 -0
  65. package/client/dist/assets/dist-js-CWeUdf5Z.js +1 -0
  66. package/client/dist/assets/dist-js-Rlh4FLH5.js +1 -0
  67. package/client/dist/assets/event-Bt7f3wXe.js +1 -0
  68. package/client/dist/assets/file-text-BcLCfcWZ.js +1 -0
  69. package/client/dist/assets/{formatDistanceToNow-BOuXgebD.js → formatDistanceToNow-C0ImhVKp.js} +1 -1
  70. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-EjGOpn4b.js → getTimezoneOffsetInMilliseconds-owhDdTXs.js} +1 -1
  71. package/client/dist/assets/git-refresh-wTW0rO0Z.js +1 -0
  72. package/client/dist/assets/i18n-DwgvwTnx.js +2 -0
  73. package/client/dist/assets/index-DhjjFyU5.js +66 -0
  74. package/client/dist/assets/index-xBFsOEiJ.css +2 -0
  75. package/client/dist/assets/jira-api-CcZMcEN5.js +1 -0
  76. package/client/dist/assets/loops-B7YpGizO.js +1 -0
  77. package/client/dist/assets/{loops-CMMQOw2P.js → loops-BomD2J-h.js} +1 -1
  78. package/client/dist/assets/{loops-B2_97D26.js → loops-BrQHSQw3.js} +1 -1
  79. package/client/dist/assets/{loops-BwxCndZk.js → loops-BvBwvTPA.js} +1 -1
  80. package/client/dist/assets/{loops-BISOkuCu.js → loops-CODk6Slz.js} +1 -1
  81. package/client/dist/assets/{loops-CD19kEzq.js → loops-DC6k-Ym-.js} +1 -1
  82. package/client/dist/assets/{loops-aEb4RxN2.js → loops-DSEg8-uY.js} +1 -1
  83. package/client/dist/assets/{loops-Bo6w_peZ.js → loops-DhCoXD_v.js} +1 -1
  84. package/client/dist/assets/narration-4Z2VUhZw.js +1 -0
  85. package/client/dist/assets/narration-BGuiEDj3.js +1 -0
  86. package/client/dist/assets/narration-BuSoqRmv.js +1 -0
  87. package/client/dist/assets/narration-CB9t29fy.js +1 -0
  88. package/client/dist/assets/narration-CU3x8FHD.js +1 -0
  89. package/client/dist/assets/narration-CWeXhfP5.js +1 -0
  90. package/client/dist/assets/narration-DRaDnhT5.js +1 -0
  91. package/client/dist/assets/narration-DtEbQgCw.js +1 -0
  92. package/client/dist/assets/packet-B1IxaL5f.js +1 -0
  93. package/client/dist/assets/packet-Bh5kkbiB.js +1 -0
  94. package/client/dist/assets/packet-By8fqYxW.js +1 -0
  95. package/client/dist/assets/packet-CZdvA44E.js +1 -0
  96. package/client/dist/assets/packet-CrjHRQCP.js +1 -0
  97. package/client/dist/assets/packet-Dwi2pTtx.js +1 -0
  98. package/client/dist/assets/packet-_-ZIa0IR.js +1 -0
  99. package/client/dist/assets/packet-n0W6E1Hn.js +1 -0
  100. package/client/dist/assets/save-B6wGWwBR.js +1 -0
  101. package/client/dist/assets/settings-Bd0x4TBa.js +1 -0
  102. package/client/dist/assets/settings-C1-7cXzC.js +1 -0
  103. package/client/dist/assets/settings-CAFaT38b.js +1 -0
  104. package/client/dist/assets/settings-Ct3v1nW2.js +1 -0
  105. package/client/dist/assets/settings-DHh7_IF5.js +1 -0
  106. package/client/dist/assets/settings-DQXAmdcv.js +1 -0
  107. package/client/dist/assets/settings-guzlhpup.js +1 -0
  108. package/client/dist/assets/settings-sZfTPMEH.js +1 -0
  109. package/client/dist/assets/{spending-BpYPsevQ.js → spending-BQX3P7Cq.js} +1 -1
  110. package/client/dist/assets/useDesktop-BzX769yH.js +1 -0
  111. package/client/dist/index.html +29 -31
  112. package/docs/internals/embedded-browser-native-webview-evaluation.md +161 -0
  113. package/docs/internals/review-packet.md +212 -0
  114. package/package.json +1 -1
  115. package/server/dist/accept-ladder.js +61 -0
  116. package/server/dist/agent-chat-manager.js +22 -1
  117. package/server/dist/agent-defaults.js +359 -0
  118. package/server/dist/agent-operator-prompt.js +13 -5
  119. package/server/dist/browser-playwright.js +39 -2
  120. package/server/dist/db.js +38 -0
  121. package/server/dist/delivery-evidence.js +242 -0
  122. package/server/dist/desktop-router.js +23 -0
  123. package/server/dist/feature-flags.js +23 -0
  124. package/server/dist/loop-command-catalog.js +33 -0
  125. package/server/dist/loop-executors.js +12 -2
  126. package/server/dist/loop-factory.js +16 -1
  127. package/server/dist/loops-router.js +2 -0
  128. package/server/dist/mcp/tools/rails.js +16 -0
  129. package/server/dist/project-registry.js +33 -1
  130. package/server/dist/project-router-loop-runs.js +27 -2
  131. package/server/dist/providers/claude-adapter.js +60 -3
  132. package/server/dist/queue-manager.js +47 -4
  133. package/server/dist/rail-isolated-launch.js +119 -5
  134. package/server/dist/rail-pr-store.js +40 -3
  135. package/server/dist/rail-worktree-release.js +17 -1
  136. package/server/dist/rails-router.js +124 -3
  137. package/server/dist/review-packet.js +327 -0
  138. package/server/dist/revision-seed.js +73 -0
  139. package/server/dist/run-duration-stats.js +71 -0
  140. package/server/dist/stuck-run-detector.js +153 -0
  141. package/server/dist/telemetry-receiver.js +22 -0
  142. package/server/dist/worktree-node-modules.js +138 -16
  143. package/client/dist/assets/ActivityFeedPage-DIDxNsDj.js +0 -1
  144. package/client/dist/assets/AgentBrowserCapture-7pJ2G5VY.js +0 -1
  145. package/client/dist/assets/AgentModeAnalyticsPane-BSSyTnTM.js +0 -2
  146. package/client/dist/assets/AgentModeCodePane-DzW6gvDw.js +0 -2
  147. package/client/dist/assets/AgentModeJobsPane-9wJff15f.js +0 -1
  148. package/client/dist/assets/AgentsPage-CIA67psb.js +0 -87
  149. package/client/dist/assets/AnalyticsPage-C0LwYELz.js +0 -1
  150. package/client/dist/assets/CodePage-RtKvHmAO.js +0 -2
  151. package/client/dist/assets/DesktopAnalyticsPage-BU8Y_Ts1.js +0 -1
  152. package/client/dist/assets/DocsDialog-DIpoaGlQ.js +0 -11
  153. package/client/dist/assets/DocsPage-ME47KZpK.js +0 -11
  154. package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +0 -1
  155. package/client/dist/assets/InteractiveJobComposer-BGjIe9R6.js +0 -19
  156. package/client/dist/assets/JobDetailModal-1UZkqH4I.js +0 -1
  157. package/client/dist/assets/JobDetailPage-CN65sAf1.js +0 -1
  158. package/client/dist/assets/JobsPage-f-4dqys7.js +0 -1
  159. package/client/dist/assets/LoopPreviewModal-DDTnTyH0.js +0 -1
  160. package/client/dist/assets/LoopsPage-BVNchIex.js +0 -1
  161. package/client/dist/assets/MinimizedChatsContext-CHsu3NCX.js +0 -1
  162. package/client/dist/assets/PluginsPage-CdFzGEfQ.js +0 -2
  163. package/client/dist/assets/TemplatePreviewModal-RDi0khFG.js +0 -1
  164. package/client/dist/assets/TicketDetailModal-MhJzrqbi.js +0 -14
  165. package/client/dist/assets/agent-Bd-q3Wqp.js +0 -1
  166. package/client/dist/assets/agent-BlvdW4Hv.js +0 -1
  167. package/client/dist/assets/agent-C-KQhxeM.js +0 -1
  168. package/client/dist/assets/agent-Ca15w5eu.js +0 -1
  169. package/client/dist/assets/agent-D6fKaoMd.js +0 -1
  170. package/client/dist/assets/agent-HFMYMTbB.js +0 -1
  171. package/client/dist/assets/agent-QIEbkBjg.js +0 -1
  172. package/client/dist/assets/agent-mTCsAbCl.js +0 -1
  173. package/client/dist/assets/api-B2M7CtDw.js +0 -1
  174. package/client/dist/assets/arrow-left-CpIR4j7G.js +0 -1
  175. package/client/dist/assets/arrow-up-right-9DoMn7fD.js +0 -1
  176. package/client/dist/assets/browser-CXUcnTMI.js +0 -1
  177. package/client/dist/assets/button-BUIIBQPT.js +0 -1
  178. package/client/dist/assets/chevron-right-CViMxucY.js +0 -1
  179. package/client/dist/assets/dashboard-BHRqsGCV.js +0 -1
  180. package/client/dist/assets/dashboard-Bfr90vOl.js +0 -1
  181. package/client/dist/assets/dashboard-CTsM8x3m.js +0 -1
  182. package/client/dist/assets/dashboard-CqC6wX0h.js +0 -1
  183. package/client/dist/assets/dashboard-CuZtAszc.js +0 -1
  184. package/client/dist/assets/dashboard-DAcqIc-n.js +0 -1
  185. package/client/dist/assets/dashboard-DekiV-86.js +0 -1
  186. package/client/dist/assets/dashboard-DoDczc5S.js +0 -1
  187. package/client/dist/assets/dialog-CzIekCw0.js +0 -45
  188. package/client/dist/assets/dist-Cez5CRkX.js +0 -1
  189. package/client/dist/assets/dist-js-BmsFWRly.js +0 -1
  190. package/client/dist/assets/dist-js-J5W_N5ng.js +0 -1
  191. package/client/dist/assets/external-link-BcU5EWZp.js +0 -1
  192. package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
  193. package/client/dist/assets/i18n-nJQvD5zJ.js +0 -2
  194. package/client/dist/assets/index-C_sdsSZt.js +0 -63
  195. package/client/dist/assets/index-DCN5QbDm.css +0 -2
  196. package/client/dist/assets/jira-api-Bhixwn-d.js +0 -1
  197. package/client/dist/assets/loops-CnxqL8IL.js +0 -1
  198. package/client/dist/assets/save-28sIlCdr.js +0 -1
  199. package/client/dist/assets/settings-BveOJoaI.js +0 -1
  200. package/client/dist/assets/settings-Cj1C6F5k.js +0 -1
  201. package/client/dist/assets/settings-CuVwOpe0.js +0 -1
  202. package/client/dist/assets/settings-D5X4DfRc.js +0 -1
  203. package/client/dist/assets/settings-DXwxe-BW.js +0 -1
  204. package/client/dist/assets/settings-Di81Ba_V.js +0 -1
  205. package/client/dist/assets/settings-jbLjCYC_.js +0 -1
  206. package/client/dist/assets/settings-wIZNPFnn.js +0 -1
  207. package/client/dist/assets/useBrowserCaptureSession-BBXBN0Na.js +0 -1
  208. package/client/dist/assets/useDesktop-B6Du2N83.js +0 -1
  209. package/client/dist/assets/useSharedWebSocket-DK96ctb7.js +0 -1
@@ -0,0 +1 @@
1
+ var e={title:`Idioma`,description:`Idioma de la UI para todo Desktop. Se aplica al instante — no hace falta reiniciar.`,selectLabel:`Idioma de la interfaz`,updateFailed:`No se pudo guardar la preferencia de idioma`},t={title:`Ajustes del proyecto`},n={saveFailed:`Error al guardar: {{message}}`},r={title:`Telemetría del pipeline`,description:`Captura el uso de tokens, la duración de las fases y la actividad de los subagentes para exportar diagnósticos. Desactivada por defecto.`,toggleLabel:`Activar telemetría del pipeline`,toggleDescription:`Cuando está activa, los datos OTEL de los jobs del pipeline se capturan en local. Usa el botón <mono>Exportar diagnóstico</mono> en cualquier tarjeta de job para descargarlos.`,enabled:`Telemetría del pipeline activada`,disabled:`Telemetría del pipeline desactivada`,saveFailed:`No se pudo guardar el ajuste de telemetría`},i={title:`Pre-prompt de rail`,description:`Instrucciones extra específicas del proyecto que se añaden a los jobs de rail implement y batch-implement, después del contexto del ticket y antes de la ejecución.`,label:`Pre-prompt`,placeholder:`Ejemplo: Prefiere cambios incrementales, mantén las migraciones retrocompatibles y añade tests para cada cambio de rail.`,helper:`Úsalo para directrices estables del proyecto que deban acompañar cada ejecución de implementación en rail.`,saveButton:`Guardar pre-prompt`,cleared:`Pre-prompt borrado`,saved:`Pre-prompt guardado`,saveFailed:`No se pudo guardar el pre-prompt`},a={title:`Pre-prompt de Freestyle`,description:`Instrucción enviada al proveedor seleccionado en Freestyle. Freestyle se salta el pipeline de OpenSpec — le entrega al proveedor este pre-prompt más el texto de la spec y le deja implementar de forma autónoma. Déjalo en blanco para usar el valor por defecto integrado.`,label:`Pre-prompt Freestyle`,placeholder:`Déjalo en blanco para usar la instrucción por defecto de Freestyle.`,helper:`El texto de la spec se añade automáticamente después de este pre-prompt. Vacío = por defecto.`,saveButton:`Guardar pre-prompt Freestyle`,resetToDefault:`Pre-prompt Freestyle restablecido al valor por defecto`,saved:`Pre-prompt Freestyle guardado`,saveFailed:`No se pudo guardar el pre-prompt Freestyle`},o={title:`Presupuesto`,description:`Define un límite de gasto diario para este proyecto. La cola se pausa automáticamente al alcanzar el límite.`,dailyLabel:`Presupuesto diario (USD)`,dailyHelper:`Déjalo en blanco para desactivarlo. El gasto se calcula sobre las últimas 24 horas.`,dailyPlaceholder:`p. ej. 5.00`,perJobLabel:`Alerta de coste por job (USD)`,perJobHelper:`Avisa cuando un único job de este proyecto supere este importe.`,perJobPlaceholder:`p. ej. 0.50`,invalidNumber:`Introduce un número positivo o déjalo en blanco para desactivar`,dailyRemoved:`Presupuesto diario eliminado`,dailySet:"Presupuesto diario fijado en ${{amount}}",perJobAlertDisabled:`Alerta de coste por job desactivada`,alertSet:"Alerta configurada para jobs de más de ${{amount}}",saveBudgetFailed:`No se pudo guardar el presupuesto`,saveThresholdFailed:`No se pudo guardar el umbral`},s={nav:{appearance:`Apariencia`,specrailsAgents:`Specrails Agents`,code:`Código`,terminal:`Terminal`,updates:`Actualizaciones`,mobile:`Móvil`,mcp:`MCP`,projects:`Proyectos`,advanced:`Avanzado`,webhooks:`Webhooks`,about:`Acerca de`},title:`Ajustes de Desktop`,description:`Gestiona los proyectos registrados y consulta la información de Desktop.`,registeredProjects:`Proyectos registrados`,noProjects:`Aún no hay proyectos registrados`,techUrlDescription:`URL base de la API de specrails-tech (por defecto: http://localhost:3000)`,techUrlSaved:`URL de specrails-tech guardada`,techUrlSaveFailed:`No se pudo guardar la URL`,budgetAlertsHeading:`Presupuesto y alertas`,dailyBudgetLabel:`Presupuesto diario de Desktop (USD)`,dailyBudgetHelper:`Límite global de gasto diario en todos los proyectos. Las colas se pausan automáticamente al superarlo.`,dailyBudgetPlaceholder:`p. ej. 10.00`,perJobHelper:`Avisa cuando un único job supere este importe. Déjalo en blanco para desactivarlo.`,costAlertsDisabled:`Alertas de coste desactivadas`,dailyBudgetRemoved:`Presupuesto diario de Desktop eliminado`,dailyBudgetSet:"Presupuesto diario de Desktop fijado en ${{amount}}",dailyBudgetSaveFailed:`No se pudo guardar el presupuesto diario de Desktop`,projectRemoved:`Proyecto eliminado`,projectRemoveFailed:`No se pudo eliminar el proyecto`,onboardingHeading:`Onboarding`,platformTour:`Tour de la plataforma`,platformTourDescription:`Vuelve a ver el asistente de bienvenida para repasar las funciones clave.`,replayTour:`Repetir tour`,terminalPanelHeading:`Panel de terminal`,infoHeading:`Información de Desktop`,infoPort:`Puerto`,infoProjects:`Proyectos`,infoDb:`BD de Desktop`},c={heading:`Notificaciones del sistema`,description:`Muestra notificaciones nativas de escritorio cuando los jobs se completan o fallan. Las notificaciones solo aparecen cuando la pestaña no tiene el foco.`,enableLabel:`Activar notificaciones del sistema`,notifyOn:`Notificar en:`,filterAll:`Todos (completados y fallidos)`,filterCompleted:`Solo completados`,filterFailed:`Solo fallidos`,enabledToast:`Notificaciones del sistema activadas`,disabledToast:`Notificaciones del sistema desactivadas`},l={heading:`Webhooks salientes`,description:`Notifica a herramientas externas (Slack, Zapier, CI/CD) los eventos de Desktop. Las peticiones se firman con <code>X-Specrails-Signature</code> cuando hay un secreto configurado.`,eventJobCompleted:`Job completado`,eventJobFailed:`Job fallido`,eventDailyBudgetExceeded:`Presupuesto diario superado`,statusOn:`on`,statusOff:`off`,disable:`Desactivar`,enable:`Activar`,sendTestPing:`Enviar ping de prueba`,addHeading:`Añadir webhook`,secretPlaceholder:`Secreto de firma (opcional)`,addButton:`Añadir webhook`,urlRequired:`La URL es obligatoria`,selectEvent:`Selecciona al menos un evento`,added:`Webhook añadido`,addFailed:`No se pudo añadir el webhook`,updateFailed:`No se pudo actualizar el webhook`,removed:`Webhook eliminado`,removeFailed:`No se pudo eliminar el webhook`,testPingSent:`Ping de prueba enviado`,testPingFailed:`No se pudo enviar el ping de prueba`},u={title:`Panel de terminal`,desktopDescription:`Valores por defecto de Desktop aplicados a todos los proyectos salvo que exista un override por proyecto.`,projectDescription:`Overrides por proyecto para el panel de terminal. Deja un campo sin cambios para heredar el valor por defecto de Desktop.`,fontFamily:`Familia tipográfica`,fontSize:`Tamaño de fuente ({{min}}–{{max}})`,renderMode:`Modo de renderizado`,copyOnSelect:`Copiar al seleccionar`,shellIntegration:`Integración con el shell (marcas OSC 133)`,notifyLongRunning:`Notificar comandos de larga duración`,longCommandThreshold:`Umbral de comando largo (ms)`,imageRendering:`Renderizado de imágenes inline (Sixel + iTerm2)`,browserShortcutUrl:`URL del atajo de navegador`,quickScript:`Script rápido (se pega en el terminal activo — pulsa Enter manualmente)`,reset:`Restablecer`,unsavedChanges:`Cambios sin guardar`,clearOverride:`Quitar override`,clear:`Quitar`,inheritingDefault:`Heredando el valor por defecto de Desktop`,nothingToSave:`Nada que guardar`,saved:`Ajustes del terminal guardados`},d={heading:`Sección de código`,summaryLanguage:`Idioma de los resúmenes`,monthlyBudget:`Presupuesto mensual (USD)`,budgetHelper:"Tope de gasto de `surface=file-summary` por mes natural. Las regeneraciones iniciadas por el usuario pueden sobrepasarlo."},f={taglines:{dracula:`El original — oscuro con tinte púrpura y acentos neón vivos`,"aurora-light":`Claro premium — índigo inspirado en Linear sobre blanco cálido`,"obsidian-dark":`Oscuro premium — casi negro con tinte azul y acentos eléctricos`,specrails:`Tema de marca — azul marino-índigo profundo con acentos cian saturados`,"code-rain":`Terminal de fósforo — menta suave sobre casi negro verdoso`,galaxy:`Azul de espacio profundo con una estela de hoja brillante`},heading:`Apariencia`,themeGroupLabel:`Tema`,currentlyActive:`Activo actualmente`,updateFailed:`No se pudo actualizar el tema`},p={heading:`App móvil`,description:`Controla Specrails desde la app SpecRails Companion de tu teléfono, 100 % a través de tu red local. Desactivado por defecto.`,accessOn:`Acceso móvil activado`,accessOff:`Acceso móvil desactivado`,listeningOnPort:`A la escucha en el puerto {{port}}`,notListening:`No está a la escucha`,turnOn:`Activar`,turnOff:`Desactivar`,pairWebDevice:`Emparejar companion web`,reset:`Restablecer`,resetConfirm:`¿Restablecer la identidad móvil? Todos los dispositivos emparejados se revocarán y tendrán que emparejarse de nuevo.`,identityReset:`Identidad móvil restablecida`,enableFailed:`No se pudo activar el acceso móvil: {{message}}`,disableFailed:`No se pudo desactivar el acceso móvil: {{message}}`,windowsFirewall:`El Firewall de Windows pedirá permiso para el servidor de SpecRails la primera vez que lo actives — elige «Permitir en redes privadas».`,pairedDevices:`Dispositivos emparejados`,noDevices:`Aún no hay dispositivos emparejados.`,revokeDevice:`Revocar {{name}}`},m={title:`Emparejar companion web`,description:`Abre specrails.dev/companion-app en tu teléfono y escanea este código con él.`,startFailed:`No se pudo iniciar el emparejamiento: {{error}}`,showThenScan:`Cuando tu teléfono haya escaneado esto, toca «Escanear código del teléfono» y apunta la cámara hacia tu teléfono.`,scanAnswer:`Escanear código del teléfono`,scanning:`Apunta la cámara al código de tu teléfono…`,cameraFailed:`Cámara no disponible: {{message}}`,notAnswer:`Ese QR no es una respuesta de emparejamiento.`,connecting:`Conectando…`,paired:`✓ Emparejado`,pairedToast:`Companion web emparejado`,answerRejected:`El equipo de escritorio rechazó ese código. Genera uno nuevo e inténtalo de nuevo.`,copyCode:`Copiar código`,codeCopied:`Código copiado`,cancel:`Cancelar`},h={heading:`Specrails Core`,installed:`Instalado:`,updateAvailable:`Actualización disponible: {{version}}`,upToDate:`Actualizado`,neverChecked:`Sin comprobar`,check:`Buscar actualizaciones`,checking:`Comprobando…`,update:`Actualizar a {{version}}`,downloading:`Descargando…`,materializing:`Instalando…`,affectsAll:`Afecta a todos los proyectos — sin reiniciar.`,unavailable:`Las actualizaciones del core no están disponibles en esta versión.`,toastDone:`Specrails Core actualizado a {{version}}`,toastError:`Error al actualizar el core: {{message}}`,toastUpToDate:`Specrails Core está actualizado`,toastCheckFailed:`Error al comprobar actualizaciones: {{message}}`},g={heading:`Specrails Desktop`,installed:`Instalada:`,check:`Buscar actualizaciones`,checking:`Comprobando…`,upToDate:`Tienes la última versión`,updateAvailable:`Actualización disponible: {{version}}`,autoHint:`Se comprueba automáticamente al arrancar y cada 6 horas.`,unavailable:`Las actualizaciones solo están disponibles en la app de escritorio.`,toastUpToDate:`Specrails Desktop está actualizado`,toastCheckFailed:`Error al buscar actualizaciones: {{message}}`},_={title:`{{name}} — Ajustes`,description:`Configuración del proyecto: prompts, rama base, entorno, presupuesto, telemetría y terminal.`,nav:{general:`General`,branch:`Rama base`,environment:`Entorno`,budget:`Presupuesto`,telemetry:`Telemetría`,terminal:`Terminal`}},v={title:`Rama de integración`,description:`La rama desde la que parten los rails paralelos y a la que apuntan sus PRs en borrador. Déjalo vacío para resolver automáticamente a la rama por defecto del repositorio.`,placeholder:`automático (rama por defecto)`,resolved:`Los rails parten de <mono>{{branch}}</mono>`,save:`Guardar rama`,saved:`Rama de integración guardada`,saveFailed:`No se pudo guardar la rama de integración`},y={title:`Entorno de worktrees`,description:`Elige los nombres de variables de entorno que este proyecto pasa a jobs de rail y worktrees aislados de loops. Solo se guardan nombres; los valores se leen al iniciar la ejecución, recuperándolos del login shell si faltan en el proceso de escritorio.`,label:`Variable de entorno`,placeholder:`NODE_AUTH_TOKEN`,helper:`Pulsa Enter o Añadir. Puedes pegar varios nombres separados por comas o espacios.`,add:`Añadir`,empty:`Aún no hay variables específicas para este proyecto.`,remove:`Quitar {{name}}`,save:`Guardar entorno`,saved:`Entorno de worktrees guardado`,saveFailed:`No se pudo guardar el entorno de worktrees`,invalidName:`{{name}} no es un nombre válido de variable de entorno`,noValues:`Añade solo nombres de variables, no secretos KEY=value.`},b={heading:`Specrails Agents`,description:`Personaliza los agentes del pipeline de specrails-core por proveedor de IA — modelo y esfuerzo de razonamiento. Los cambios aplican a la siguiente ejecución, sin reiniciar.`,loadFailed:`No se pudieron cargar los ajustes de agentes.`,modeDefault:`Por defecto`,modeCustom:`Personalizado`,notDetected:`No detectado`,notSignedIn:`Sin iniciar sesión`,pipeline:`Pipeline`,pipelineHint:`Modelo y esfuerzo de toda la ejecución — los subagentes los heredan salvo que los cambies abajo.`,model:`Modelo`,effort:`Esfuerzo`,defaultModel:`Por defecto ({{model}})`,defaultEffort:`Esfuerzo por defecto`,agentsTitle:`Agentes del pipeline`,agentsHint:`Modelo por agente para este proveedor.`,inheritNote:`Los subagentes de Codex heredan el modelo y esfuerzo del pipeline por diseño.`,noPerAgentNote:`Este proveedor no admite modelos por agente.`,reset:`Restaurar valores por defecto`,agentNames:{"sr-architect":`Arquitecto`,"sr-developer":`Desarrollador`,"sr-reviewer":`Revisor`}},x={language:e,page:t,errors:n,telemetry:r,prePrompt:i,freestylePrePrompt:a,budget:o,desktop:s,notifications:c,webhooks:l,terminal:u,codeSection:d,appearance:f,mobile:p,pairWeb:m,coreUpdate:h,appUpdate:g,projectDialog:_,integrationBranch:v,worktreeEnv:y,specrailsAgents:b};export{g as appUpdate,f as appearance,o as budget,d as codeSection,h as coreUpdate,x as default,s as desktop,n as errors,a as freestylePrePrompt,v as integrationBranch,e as language,p as mobile,c as notifications,t as page,m as pairWeb,i as prePrompt,_ as projectDialog,b as specrailsAgents,r as telemetry,u as terminal,l as webhooks,y as worktreeEnv};
@@ -0,0 +1 @@
1
+ var e={title:`Idioma`,description:`Idioma da UI em todo o Desktop. Aplica-se de imediato — sem reiniciar.`,selectLabel:`Idioma da interface`,updateFailed:`Não foi possível guardar a preferência de idioma`},t={title:`Definições do projeto`},n={saveFailed:`Falha ao guardar: {{message}}`},r={title:`Telemetria do pipeline`,description:`Capture uso de tokens, durações de fases e atividade de subagentes para exportação de diagnóstico. Desativada por omissão.`,toggleLabel:`Ativar telemetria do pipeline`,toggleDescription:`Quando ativa, os dados OTEL dos jobs do pipeline são capturados localmente. Use o botão <mono>Exportar diagnóstico</mono> em qualquer cartão de job para descarregar.`,enabled:`Telemetria do pipeline ativada`,disabled:`Telemetria do pipeline desativada`,saveFailed:`Falha ao guardar a definição de telemetria`},i={title:`Pre-prompt de rail`,description:`Instruções extra específicas do projeto, acrescentadas aos jobs de rail implement e batch-implement depois do contexto do ticket e antes da execução.`,label:`Pre-prompt`,placeholder:`Exemplo: Prefira alterações incrementais, mantenha as migrações retrocompatíveis e adicione testes a cada alteração de rail.`,helper:`Use isto para orientações estáveis do projeto que devem acompanhar cada execução de implementação em rail.`,saveButton:`Guardar pre-prompt`,cleared:`Pre-prompt limpo`,saved:`Pre-prompt guardado`,saveFailed:`Falha ao guardar o pre-prompt`},a={title:`Pre-prompt de Freestyle`,description:`Instrução enviada ao fornecedor selecionado em Freestyle. O Freestyle salta o pipeline OpenSpec — entrega ao fornecedor este pre-prompt mais o texto da spec e deixa-o implementar de forma autónoma. Deixe em branco para usar o valor predefinido.`,label:`Pre-prompt Freestyle`,placeholder:`Deixe em branco para usar a instrução Freestyle predefinida.`,helper:`O texto da spec é acrescentado automaticamente depois deste pre-prompt. Vazio = predefinição.`,saveButton:`Guardar pre-prompt Freestyle`,resetToDefault:`Pre-prompt Freestyle reposto na predefinição`,saved:`Pre-prompt Freestyle guardado`,saveFailed:`Falha ao guardar o pre-prompt Freestyle`},o={title:`Orçamento`,description:`Defina um limite de gasto diário para este projeto. A fila pausa automaticamente quando o limite é atingido.`,dailyLabel:`Orçamento diário (USD)`,dailyHelper:`Deixe em branco para desativar. O gasto é calculado sobre as últimas 24 horas.`,dailyPlaceholder:`ex.: 5.00`,perJobLabel:`Alerta de custo por job (USD)`,perJobHelper:`Alertar quando um único job deste projeto excede este valor.`,perJobPlaceholder:`ex.: 0.50`,invalidNumber:`Introduza um número positivo ou deixe em branco para desativar`,dailyRemoved:`Orçamento diário removido`,dailySet:"Orçamento diário definido como ${{amount}}",perJobAlertDisabled:`Alerta de custo por job desativado`,alertSet:"Alerta definido para jobs acima de ${{amount}}",saveBudgetFailed:`Falha ao guardar o orçamento`,saveThresholdFailed:`Falha ao guardar o limiar`},s={nav:{appearance:`Aparência`,specrailsAgents:`Specrails Agents`,code:`Código`,terminal:`Terminal`,updates:`Atualizações`,mobile:`Móvel`,mcp:`MCP`,projects:`Projetos`,advanced:`Avançado`,webhooks:`Webhooks`,about:`Sobre`},title:`Definições do Desktop`,description:`Faça a gestão dos projetos registados e consulte informações do Desktop.`,registeredProjects:`Projetos registados`,noProjects:`Ainda não há projetos registados`,techUrlDescription:`URL base para a API specrails-tech (predefinição: http://localhost:3000)`,techUrlSaved:`URL do specrails-tech guardado`,techUrlSaveFailed:`Falha ao guardar o URL`,budgetAlertsHeading:`Orçamento e alertas`,dailyBudgetLabel:`Orçamento diário do Desktop (USD)`,dailyBudgetHelper:`Limite global de gasto diário em todos os projetos. As filas pausam automaticamente quando é excedido.`,dailyBudgetPlaceholder:`ex.: 10.00`,perJobHelper:`Alertar quando um único job excede este valor. Deixe em branco para desativar.`,costAlertsDisabled:`Alertas de custo desativados`,dailyBudgetRemoved:`Orçamento diário do Desktop removido`,dailyBudgetSet:"Orçamento diário do Desktop definido como ${{amount}}",dailyBudgetSaveFailed:`Falha ao guardar o orçamento diário do Desktop`,projectRemoved:`Projeto removido`,projectRemoveFailed:`Falha ao remover o projeto`,onboardingHeading:`Onboarding`,platformTour:`Tour da plataforma`,platformTourDescription:`Repita o assistente de boas-vindas para rever as funcionalidades principais.`,replayTour:`Repetir tour`,terminalPanelHeading:`Painel de terminal`,infoHeading:`Informações do Desktop`,infoPort:`Porta`,infoProjects:`Projetos`,infoDb:`BD do Desktop`},c={heading:`Notificações do SO`,description:`Mostrar notificações nativas do desktop quando os jobs terminam ou falham. As notificações só aparecem quando o separador não está focado.`,enableLabel:`Ativar notificações do SO`,notifyOn:`Notificar em:`,filterAll:`Tudo (concluídos e falhados)`,filterCompleted:`Apenas concluídos`,filterFailed:`Apenas falhados`,enabledToast:`Notificações do SO ativadas`,disabledToast:`Notificações do SO desativadas`},l={heading:`Webhooks de saída`,description:`Notifique ferramentas externas (Slack, Zapier, CI/CD) sobre eventos do Desktop. Os pedidos são assinados via <code>X-Specrails-Signature</code> quando há um segredo definido.`,eventJobCompleted:`Job concluído`,eventJobFailed:`Job falhado`,eventDailyBudgetExceeded:`Orçamento diário excedido`,statusOn:`ligado`,statusOff:`desligado`,disable:`Desativar`,enable:`Ativar`,sendTestPing:`Enviar ping de teste`,addHeading:`Adicionar webhook`,secretPlaceholder:`Segredo de assinatura (opcional)`,addButton:`Adicionar webhook`,urlRequired:`O URL é obrigatório`,selectEvent:`Selecione pelo menos um evento`,added:`Webhook adicionado`,addFailed:`Falha ao adicionar o webhook`,updateFailed:`Falha ao atualizar o webhook`,removed:`Webhook removido`,removeFailed:`Falha ao remover o webhook`,testPingSent:`Ping de teste enviado`,testPingFailed:`Falha ao enviar o ping de teste`},u={title:`Painel de terminal`,desktopDescription:`Predefinições do Desktop aplicadas a todos os projetos, salvo override por projeto.`,projectDescription:`Overrides por projeto para o painel de terminal. Deixe um campo inalterado para herdar a predefinição do Desktop.`,fontFamily:`Tipo de letra`,fontSize:`Tamanho da letra ({{min}}–{{max}})`,renderMode:`Modo de renderização`,copyOnSelect:`Copiar ao selecionar`,shellIntegration:`Integração com a shell (marcas OSC 133)`,notifyLongRunning:`Notificar comandos de longa duração`,longCommandThreshold:`Limiar de comando longo (ms)`,imageRendering:`Renderização de imagens inline (Sixel + iTerm2)`,browserShortcutUrl:`URL do atalho do browser`,quickScript:`Script rápido (colado no terminal ativo — Enter manualmente)`,reset:`Repor`,unsavedChanges:`Alterações não guardadas`,clearOverride:`Limpar override`,clear:`Limpar`,inheritingDefault:`A herdar a predefinição do Desktop`,nothingToSave:`Nada para guardar`,saved:`Definições do terminal guardadas`},d={heading:`Secção de código`,summaryLanguage:`Idioma dos resumos`,monthlyBudget:`Orçamento mensal (USD)`,budgetHelper:"Limite de gasto em `surface=file-summary` por mês de calendário. Regenerações iniciadas pelo utilizador podem ignorá-lo."},f={taglines:{dracula:`O original — escuro com tom púrpura e acentos néon vivos`,"aurora-light":`Claro premium — índigo inspirado no Linear sobre branco quente`,"obsidian-dark":`Escuro premium — quase preto com tom azulado e acentos elétricos`,specrails:`Tema da marca — azul-marinho-índigo profundo com acentos ciano saturados`,"code-rain":`Terminal de fósforo — menta suave sobre quase preto esverdeado`,galaxy:`Azul de espaço profundo com um rastro brilhante de lâmina`},heading:`Aspeto`,themeGroupLabel:`Tema`,currentlyActive:`Atualmente ativo`,updateFailed:`Falha ao atualizar o tema`},p={heading:`Companion móvel`,description:`Controle o Specrails a partir da app SpecRails Companion no seu telemóvel, 100% através da sua rede local. Desativado por omissão.`,accessOn:`Acesso móvel ligado`,accessOff:`Acesso móvel desligado`,listeningOnPort:`À escuta na porta {{port}}`,notListening:`Não está à escuta`,turnOn:`Ligar`,turnOff:`Desligar`,pairWebDevice:`Emparelhar companion web`,reset:`Repor`,resetConfirm:`Repor a identidade móvel? Todos os dispositivos emparelhados serão revogados e terão de emparelhar novamente.`,identityReset:`Identidade móvel reposta`,enableFailed:`Não foi possível ativar o acesso móvel: {{message}}`,disableFailed:`Não foi possível desativar o acesso móvel: {{message}}`,windowsFirewall:`A Firewall do Windows pedirá para permitir o servidor SpecRails na primeira ativação — escolha “Permitir em redes privadas”.`,pairedDevices:`Dispositivos emparelhados`,noDevices:`Ainda não há dispositivos emparelhados.`,revokeDevice:`Revogar {{name}}`},m={title:`Emparelhar companion web`,description:`Abra specrails.dev/companion-app no seu telemóvel e leia este código com ele.`,startFailed:`Não foi possível iniciar o emparelhamento: {{error}}`,showThenScan:`Depois de o seu telemóvel ler isto, toque em “Ler código do telemóvel” e aponte a câmara para o telemóvel.`,scanAnswer:`Ler código do telemóvel`,scanning:`Aponte a câmara para o código no seu telemóvel…`,cameraFailed:`Câmara indisponível: {{message}}`,notAnswer:`Esse QR não é uma resposta de emparelhamento.`,connecting:`A ligar…`,paired:`✓ Emparelhado`,pairedToast:`Companion web emparelhado`,answerRejected:`O desktop rejeitou esse código. Gere um novo e tente novamente.`,copyCode:`Copiar código`,codeCopied:`Código copiado`,cancel:`Cancelar`},h={heading:`Specrails Core`,installed:`Instalado:`,updateAvailable:`Atualização disponível: {{version}}`,upToDate:`Atualizado`,neverChecked:`Não verificado`,check:`Procurar atualizações`,checking:`A verificar…`,update:`Atualizar para {{version}}`,downloading:`A descarregar…`,materializing:`A instalar…`,affectsAll:`Aplica-se a todos os projetos — sem reiniciar.`,unavailable:`As atualizações do core não estão disponíveis nesta versão.`,toastDone:`Specrails Core atualizado para {{version}}`,toastError:`Falha na atualização do core: {{message}}`,toastUpToDate:`O Specrails Core está atualizado`,toastCheckFailed:`Falha ao verificar atualizações: {{message}}`},g={heading:`Specrails Desktop`,installed:`Instalada:`,check:`Verificar atualizações`,checking:`Verificando…`,upToDate:`Você está na versão mais recente`,updateAvailable:`Atualização disponível: {{version}}`,autoHint:`Verificação automática ao iniciar e a cada 6 horas.`,unavailable:`Atualizações estão disponíveis apenas no app desktop.`,toastUpToDate:`O Specrails Desktop está atualizado`,toastCheckFailed:`Falha ao verificar atualizações: {{message}}`},_={title:`{{name}} — Configurações`,description:`Configuração do projeto: prompts, branch, ambiente, orçamento, telemetria e terminal.`,nav:{general:`Geral`,branch:`Branch`,environment:`Ambiente`,budget:`Orçamento`,telemetry:`Telemetria`,terminal:`Terminal`}},v={title:`Ramo de integração`,description:`O ramo a partir do qual os rails paralelos derivam e para o qual apontam os seus PRs em rascunho. Deixe vazio para resolver automaticamente para o ramo padrão do repositório.`,placeholder:`automático (ramo padrão)`,resolved:`Os rails derivam de <mono>{{branch}}</mono>`,save:`Guardar ramo`,saved:`Ramo de integração guardado`,saveFailed:`Não foi possível guardar o ramo de integração`},y={title:`Ambiente dos worktrees`,description:`Escolha os nomes das variáveis de ambiente que este projeto passa para jobs de rail e worktrees isolados de loops. Só os nomes são guardados; os valores são lidos quando a execução começa, com recuperação a partir do login shell se faltarem no processo Desktop.`,label:`Variável de ambiente`,placeholder:`NODE_AUTH_TOKEN`,helper:`Prima Enter ou Adicionar. Pode colar vários nomes separados por vírgulas ou espaços.`,add:`Adicionar`,empty:`Ainda não há variáveis específicas para este projeto.`,remove:`Remover {{name}}`,save:`Guardar ambiente`,saved:`Ambiente dos worktrees guardado`,saveFailed:`Não foi possível guardar o ambiente dos worktrees`,invalidName:`{{name}} não é um nome válido de variável de ambiente`,noValues:`Adicione apenas nomes de variáveis, não segredos KEY=value.`},b={heading:`Specrails Agents`,description:`Personalize os agentes do pipeline specrails-core por provedor de IA — modelo e esforço de raciocínio. As mudanças valem para a próxima execução, sem reiniciar.`,loadFailed:`Não foi possível carregar as configurações dos agentes.`,modeDefault:`Padrão`,modeCustom:`Personalizado`,notDetected:`Não detectado`,notSignedIn:`Sem sessão iniciada`,pipeline:`Pipeline`,pipelineHint:`Modelo e esforço de toda a execução — os subagentes os herdam, salvo override abaixo.`,model:`Modelo`,effort:`Esforço`,defaultModel:`Padrão ({{model}})`,defaultEffort:`Esforço padrão`,agentsTitle:`Agentes do pipeline`,agentsHint:`Modelo por agente para este provedor.`,inheritNote:`Os subagentes do Codex herdam o modelo e o esforço do pipeline por design.`,noPerAgentNote:`Este provedor não suporta modelos por agente.`,reset:`Restaurar padrões`,agentNames:{"sr-architect":`Arquiteto`,"sr-developer":`Desenvolvedor`,"sr-reviewer":`Revisor`}},x={language:e,page:t,errors:n,telemetry:r,prePrompt:i,freestylePrePrompt:a,budget:o,desktop:s,notifications:c,webhooks:l,terminal:u,codeSection:d,appearance:f,mobile:p,pairWeb:m,coreUpdate:h,appUpdate:g,projectDialog:_,integrationBranch:v,worktreeEnv:y,specrailsAgents:b};export{g as appUpdate,f as appearance,o as budget,d as codeSection,h as coreUpdate,x as default,s as desktop,n as errors,a as freestylePrePrompt,v as integrationBranch,e as language,p as mobile,c as notifications,t as page,m as pairWeb,i as prePrompt,_ as projectDialog,b as specrailsAgents,r as telemetry,u as terminal,l as webhooks,y as worktreeEnv};
@@ -0,0 +1 @@
1
+ var e={title:`语言`,description:`Desktop 全局界面语言。即时生效,无需重启。`,selectLabel:`界面语言`,updateFailed:`无法保存语言偏好`},t={title:`项目设置`},n={saveFailed:`保存失败:{{message}}`},r={title:`流水线遥测`,description:`采集 token 用量、阶段耗时和子 Agent 活动以供诊断导出。默认关闭。`,toggleLabel:`启用流水线遥测`,toggleDescription:`开启后,流水线任务的 OTEL 数据将在本地采集。在任意任务卡片上使用<mono>导出诊断</mono>按钮即可下载。`,enabled:`已启用流水线遥测`,disabled:`已禁用流水线遥测`,saveFailed:`保存遥测设置失败`},i={title:`Rail 前置提示词`,description:`追加到 implement 与 batch-implement rail 任务中的项目专属附加指令,位于工单上下文之后、开始执行之前。`,label:`前置提示词`,placeholder:`示例:优先采用增量修改,保持迁移向后兼容,并为每次 rail 变更添加测试。`,helper:`用于应随每次 rail 实现运行附带的稳定项目指引。`,saveButton:`保存前置提示词`,cleared:`前置提示词已清除`,saved:`前置提示词已保存`,saveFailed:`保存前置提示词失败`},a={title:`Freestyle 前置提示词`,description:`在 Freestyle 模式下发送给所选提供方的指令。Freestyle 跳过 OpenSpec 流水线——直接把该前置提示词加上 spec 文本交给提供方,让其自主实现。留空则使用内置默认值。`,label:`Freestyle 前置提示词`,placeholder:`留空以使用默认的 Freestyle 指令。`,helper:`spec 文本会自动追加在该前置提示词之后。留空 = 使用默认值。`,saveButton:`保存 Freestyle 前置提示词`,resetToDefault:`Freestyle 前置提示词已重置为默认值`,saved:`Freestyle 前置提示词已保存`,saveFailed:`保存 Freestyle 前置提示词失败`},o={title:`预算`,description:`为此项目设置每日支出上限。达到上限时队列自动暂停。`,dailyLabel:`每日预算(USD)`,dailyHelper:`留空表示禁用。支出按最近 24 小时计算。`,dailyPlaceholder:`如 5.00`,perJobLabel:`单任务成本告警(USD)`,perJobHelper:`当此项目中的单个任务超过此金额时告警。`,perJobPlaceholder:`如 0.50`,invalidNumber:`请输入正数,或留空以禁用`,dailyRemoved:`已移除每日预算`,dailySet:"每日预算已设为 ${{amount}}",perJobAlertDisabled:`已禁用单任务成本告警`,alertSet:"已设置任务超过 ${{amount}} 时告警",saveBudgetFailed:`保存预算失败`,saveThresholdFailed:`保存阈值失败`},s={nav:{appearance:`外观`,specrailsAgents:`Specrails Agents`,code:`代码`,terminal:`终端`,updates:`更新`,mobile:`移动端`,mcp:`MCP`,projects:`项目`,advanced:`高级`,webhooks:`Webhooks`,about:`关于`},title:`Desktop 设置`,description:`管理已注册的项目并查看 Desktop 信息。`,registeredProjects:`已注册项目`,noProjects:`尚未注册任何项目`,techUrlDescription:`specrails-tech API 的基础 URL(默认:http://localhost:3000)`,techUrlSaved:`specrails-tech URL 已保存`,techUrlSaveFailed:`保存 URL 失败`,budgetAlertsHeading:`预算与告警`,dailyBudgetLabel:`Desktop 每日预算(USD)`,dailyBudgetHelper:`所有项目的全局每日支出上限。超出后队列自动暂停。`,dailyBudgetPlaceholder:`如 10.00`,perJobHelper:`当单个任务超过此金额时告警。留空表示禁用。`,costAlertsDisabled:`已禁用成本告警`,dailyBudgetRemoved:`已移除 Desktop 每日预算`,dailyBudgetSet:"Desktop 每日预算已设为 ${{amount}}",dailyBudgetSaveFailed:`保存 Desktop 每日预算失败`,projectRemoved:`项目已移除`,projectRemoveFailed:`移除项目失败`,onboardingHeading:`新手引导`,platformTour:`平台导览`,platformTourDescription:`重播欢迎向导,回顾核心功能。`,replayTour:`重播导览`,terminalPanelHeading:`终端面板`,infoHeading:`Desktop 信息`,infoPort:`端口`,infoProjects:`项目`,infoDb:`Desktop 数据库`},c={heading:`系统通知`,description:`任务完成或失败时显示原生桌面通知。仅在标签页未聚焦时显示通知。`,enableLabel:`启用系统通知`,notifyOn:`通知时机:`,filterAll:`全部(完成与失败)`,filterCompleted:`仅完成`,filterFailed:`仅失败`,enabledToast:`已启用系统通知`,disabledToast:`已禁用系统通知`},l={heading:`出站 Webhook`,description:`在 Desktop 事件发生时通知外部工具(Slack、Zapier、CI/CD)。设置密钥后,请求会通过 <code>X-Specrails-Signature</code> 进行签名。`,eventJobCompleted:`任务完成`,eventJobFailed:`任务失败`,eventDailyBudgetExceeded:`超出每日预算`,statusOn:`开`,statusOff:`关`,disable:`禁用`,enable:`启用`,sendTestPing:`发送测试 ping`,addHeading:`添加 Webhook`,secretPlaceholder:`签名密钥(可选)`,addButton:`添加 Webhook`,urlRequired:`URL 为必填项`,selectEvent:`请至少选择一个事件`,added:`Webhook 已添加`,addFailed:`添加 Webhook 失败`,updateFailed:`更新 Webhook 失败`,removed:`Webhook 已移除`,removeFailed:`移除 Webhook 失败`,testPingSent:`测试 ping 已发送`,testPingFailed:`发送测试 ping 失败`},u={title:`终端面板`,desktopDescription:`Desktop 全局默认值,应用于所有项目,除非设置了项目级覆盖。`,projectDescription:`终端面板的项目级覆盖。保持字段不变即继承 Desktop 默认值。`,fontFamily:`字体`,fontSize:`字号({{min}}–{{max}})`,renderMode:`渲染模式`,copyOnSelect:`选中即复制`,shellIntegration:`Shell 集成(OSC 133 标记)`,notifyLongRunning:`长耗时命令通知`,longCommandThreshold:`长命令阈值(ms)`,imageRendering:`内联图片渲染(Sixel + iTerm2)`,browserShortcutUrl:`浏览器快捷方式 URL`,quickScript:`快捷脚本(粘贴到活动终端——需手动按 Enter)`,reset:`重置`,unsavedChanges:`有未保存的更改`,clearOverride:`清除覆盖`,clear:`清除`,inheritingDefault:`正在继承 Desktop 默认值`,nothingToSave:`没有可保存的内容`,saved:`终端设置已保存`},d={heading:`代码板块`,summaryLanguage:`摘要语言`,monthlyBudget:`每月预算(USD)`,budgetHelper:"每个自然月 `surface=file-summary` 支出的上限。用户手动发起的重新生成可超出此限制。"},f={taglines:{dracula:`经典原版 — 紫色调暗色主题,霓虹色点缀鲜明`,"aurora-light":`高级浅色 — 受 Linear 启发的靛蓝配暖白底色`,"obsidian-dark":`高级深色 — 近黑蓝调配电光色点缀`,specrails:`品牌主题 — 深海军靛蓝配饱和青色点缀`,"code-rain":`磷光终端 — 绿黑底色上的柔和薄荷绿`,galaxy:`深空蓝配上发光的光刃轨迹`},heading:`外观`,themeGroupLabel:`主题`,currentlyActive:`当前使用中`,updateFailed:`更新主题失败`},p={heading:`移动伴侣`,description:`通过手机上的 SpecRails Companion 应用控制 Specrails,完全经由本地网络。默认关闭。`,accessOn:`移动访问已开启`,accessOff:`移动访问已关闭`,listeningOnPort:`正在监听端口 {{port}}`,notListening:`未在监听`,turnOn:`开启`,turnOff:`关闭`,pairWebDevice:`配对网页伴侣`,reset:`重置`,resetConfirm:`确定要重置移动身份吗?所有已配对设备都将被吊销,必须重新配对。`,identityReset:`移动身份已重置`,enableFailed:`无法启用移动访问:{{message}}`,disableFailed:`无法禁用移动访问:{{message}}`,windowsFirewall:`首次启用时,Windows 防火墙会询问是否允许 SpecRails 服务器——请选择“允许在专用网络上”。`,pairedDevices:`已配对设备`,noDevices:`尚无已配对设备。`,revokeDevice:`吊销 {{name}}`},m={title:`配对网页伴侣`,description:`在手机上打开 specrails.dev/companion-app,然后用它扫描此二维码。`,startFailed:`无法开始配对:{{error}}`,showThenScan:`手机扫描此码后,点按“扫描手机的二维码”,并将摄像头对准手机。`,scanAnswer:`扫描手机的二维码`,scanning:`请将摄像头对准手机上的二维码…`,cameraFailed:`摄像头不可用:{{message}}`,notAnswer:`该二维码不是配对应答。`,connecting:`正在连接…`,paired:`✓ 已配对`,pairedToast:`网页伴侣已配对`,answerRejected:`桌面端拒绝了该码。请重新生成一个并重试。`,copyCode:`复制配对码`,codeCopied:`配对码已复制`,cancel:`取消`},h={heading:`Specrails Core`,installed:`已安装:`,updateAvailable:`有可用更新:{{version}}`,upToDate:`已是最新`,neverChecked:`尚未检查`,check:`检查更新`,checking:`检查中…`,update:`更新到 {{version}}`,downloading:`下载中…`,materializing:`安装中…`,affectsAll:`适用于所有项目 — 无需重启。`,unavailable:`此版本不支持核心更新。`,toastDone:`Specrails Core 已更新到 {{version}}`,toastError:`核心更新失败:{{message}}`,toastUpToDate:`Specrails Core 已是最新`,toastCheckFailed:`检查更新失败:{{message}}`},g={heading:`Specrails Desktop`,installed:`已安装:`,check:`检查更新`,checking:`检查中…`,upToDate:`已是最新版本`,updateAvailable:`有可用更新: {{version}}`,autoHint:`启动时及每 6 小时自动检查。`,unavailable:`更新仅在桌面应用中可用。`,toastUpToDate:`Specrails Desktop 已是最新`,toastCheckFailed:`检查更新失败: {{message}}`},_={title:`{{name}} — 设置`,description:`项目级配置:提示词、分支、环境、预算、遥测和终端。`,nav:{general:`常规`,branch:`分支`,environment:`环境`,budget:`预算`,telemetry:`遥测`,terminal:`终端`}},v={title:`集成分支`,description:`并行 rail 从此分支创建,其草稿 PR 也以此为目标。留空则自动解析为仓库的默认分支。`,placeholder:`自动(仓库默认分支)`,resolved:`Rail 从 <mono>{{branch}}</mono> 创建分支`,save:`保存分支`,saved:`集成分支已保存`,saveFailed:`无法保存集成分支`},y={title:`Worktree 环境`,description:`选择此项目传递给 rail job 和隔离 loop worktree 的环境变量名称。只保存名称;值会在运行开始时读取,如果 Desktop 进程中缺失,会尝试从 login shell 恢复。`,label:`环境变量`,placeholder:`NODE_AUTH_TOKEN`,helper:`按 Enter 或添加。可以粘贴多个用逗号或空格分隔的名称。`,add:`添加`,empty:`尚未为此项目配置专用变量。`,remove:`移除 {{name}}`,save:`保存环境`,saved:`Worktree 环境已保存`,saveFailed:`无法保存 Worktree 环境`,invalidName:`{{name}} 不是有效的环境变量名`,noValues:`这里只添加变量名,不要添加 KEY=value 形式的密钥。`},b={heading:`Specrails Agents`,description:`按 AI 提供商自定义 specrails-core 流水线代理 — 模型与推理力度。更改在下次运行时生效,无需重启。`,loadFailed:`无法加载代理默认设置。`,modeDefault:`默认`,modeCustom:`自定义`,notDetected:`未检测到`,notSignedIn:`未登录`,pipeline:`流水线`,pipelineHint:`整次运行的模型与力度 — 除非在下方覆盖,子代理将继承它们。`,model:`模型`,effort:`力度`,defaultModel:`默认({{model}})`,defaultEffort:`默认力度`,agentsTitle:`流水线代理`,agentsHint:`此提供商的按代理模型覆盖。`,inheritNote:`Codex 子代理按设计继承流水线的模型与力度。`,noPerAgentNote:`此提供商不支持按代理设置模型。`,reset:`恢复默认`,agentNames:{"sr-architect":`架构师`,"sr-developer":`开发者`,"sr-reviewer":`审查者`}},x={language:e,page:t,errors:n,telemetry:r,prePrompt:i,freestylePrePrompt:a,budget:o,desktop:s,notifications:c,webhooks:l,terminal:u,codeSection:d,appearance:f,mobile:p,pairWeb:m,coreUpdate:h,appUpdate:g,projectDialog:_,integrationBranch:v,worktreeEnv:y,specrailsAgents:b};export{g as appUpdate,f as appearance,o as budget,d as codeSection,h as coreUpdate,x as default,s as desktop,n as errors,a as freestylePrePrompt,v as integrationBranch,e as language,p as mobile,c as notifications,t as page,m as pairWeb,i as prePrompt,_ as projectDialog,b as specrailsAgents,r as telemetry,u as terminal,l as webhooks,y as worktreeEnv};
@@ -1 +1 @@
1
- import{o as e}from"./i18n-nJQvD5zJ.js";var t={job:`analytics:surfaces.job`,"quick-spec":`analytics:surfaces.quickSpec`,"explore-spec":`analytics:surfaces.exploreSpec`,"ai-edit":`analytics:surfaces.aiEdit`,smash:`analytics:surfaces.smash`,"file-summary":`analytics:surfaces.fileSummary`,loop:`analytics:surfaces.loop`,"chat-sidebar":`analytics:surfaces.chatSidebar`,"spec-launcher":`analytics:surfaces.specLauncher`,proposal:`analytics:surfaces.proposal`,"agent-studio":`analytics:surfaces.agentStudio`,setup:`analytics:surfaces.setup`},n={bg:`bg-muted/40`,text:`text-muted-foreground`,ring:`ring-border/40`,dot:`bg-muted-foreground`},r={job:{bg:`bg-accent-info/15`,text:`text-accent-info`,ring:`ring-accent-info/40`,dot:`bg-accent-info`},"quick-spec":{bg:`bg-accent-secondary/15`,text:`text-accent-secondary`,ring:`ring-accent-secondary/40`,dot:`bg-accent-secondary`},"explore-spec":{bg:`bg-accent-highlight/15`,text:`text-accent-highlight`,ring:`ring-accent-highlight/40`,dot:`bg-accent-highlight`},"ai-edit":{bg:`bg-accent-success/15`,text:`text-accent-success`,ring:`ring-accent-success/40`,dot:`bg-accent-success`},smash:{bg:`bg-accent-highlight/15`,text:`text-accent-highlight`,ring:`ring-accent-highlight/40`,dot:`bg-accent-highlight`},"file-summary":{bg:`bg-accent-warning/15`,text:`text-accent-warning`,ring:`ring-accent-warning/40`,dot:`bg-accent-warning`},loop:{bg:`bg-accent-primary/15`,text:`text-accent-primary`,ring:`ring-accent-primary/40`,dot:`bg-accent-primary`},"chat-sidebar":{bg:`bg-accent-info/15`,text:`text-accent-info`,ring:`ring-accent-info/40`,dot:`bg-accent-info`},"spec-launcher":{bg:`bg-accent-secondary/15`,text:`text-accent-secondary`,ring:`ring-accent-secondary/40`,dot:`bg-accent-secondary`},proposal:{bg:`bg-accent-highlight/15`,text:`text-accent-highlight`,ring:`ring-accent-highlight/40`,dot:`bg-accent-highlight`},"agent-studio":{bg:`bg-accent-success/15`,text:`text-accent-success`,ring:`ring-accent-success/40`,dot:`bg-accent-success`},setup:{bg:`bg-accent-primary/15`,text:`text-accent-primary`,ring:`ring-accent-primary/40`,dot:`bg-accent-primary`}};function i(e,t){return new Proxy(e,{get(e,n,r){return typeof n==`string`&&!(n in e)?t(n):Reflect.get(e,n,r)}})}var a=i(Object.defineProperties({},Object.fromEntries(Object.keys(t).map(n=>[n,{get:()=>e.t(t[n]),enumerable:!0}]))),e=>e),o=i(r,()=>n);export{a as n,o as t};
1
+ import{o as e}from"./i18n-DwgvwTnx.js";var t={job:`analytics:surfaces.job`,"quick-spec":`analytics:surfaces.quickSpec`,"explore-spec":`analytics:surfaces.exploreSpec`,"ai-edit":`analytics:surfaces.aiEdit`,smash:`analytics:surfaces.smash`,"file-summary":`analytics:surfaces.fileSummary`,loop:`analytics:surfaces.loop`,"chat-sidebar":`analytics:surfaces.chatSidebar`,"spec-launcher":`analytics:surfaces.specLauncher`,proposal:`analytics:surfaces.proposal`,"agent-studio":`analytics:surfaces.agentStudio`,setup:`analytics:surfaces.setup`},n={bg:`bg-muted/40`,text:`text-muted-foreground`,ring:`ring-border/40`,dot:`bg-muted-foreground`},r={job:{bg:`bg-accent-info/15`,text:`text-accent-info`,ring:`ring-accent-info/40`,dot:`bg-accent-info`},"quick-spec":{bg:`bg-accent-secondary/15`,text:`text-accent-secondary`,ring:`ring-accent-secondary/40`,dot:`bg-accent-secondary`},"explore-spec":{bg:`bg-accent-highlight/15`,text:`text-accent-highlight`,ring:`ring-accent-highlight/40`,dot:`bg-accent-highlight`},"ai-edit":{bg:`bg-accent-success/15`,text:`text-accent-success`,ring:`ring-accent-success/40`,dot:`bg-accent-success`},smash:{bg:`bg-accent-highlight/15`,text:`text-accent-highlight`,ring:`ring-accent-highlight/40`,dot:`bg-accent-highlight`},"file-summary":{bg:`bg-accent-warning/15`,text:`text-accent-warning`,ring:`ring-accent-warning/40`,dot:`bg-accent-warning`},loop:{bg:`bg-accent-primary/15`,text:`text-accent-primary`,ring:`ring-accent-primary/40`,dot:`bg-accent-primary`},"chat-sidebar":{bg:`bg-accent-info/15`,text:`text-accent-info`,ring:`ring-accent-info/40`,dot:`bg-accent-info`},"spec-launcher":{bg:`bg-accent-secondary/15`,text:`text-accent-secondary`,ring:`ring-accent-secondary/40`,dot:`bg-accent-secondary`},proposal:{bg:`bg-accent-highlight/15`,text:`text-accent-highlight`,ring:`ring-accent-highlight/40`,dot:`bg-accent-highlight`},"agent-studio":{bg:`bg-accent-success/15`,text:`text-accent-success`,ring:`ring-accent-success/40`,dot:`bg-accent-success`},setup:{bg:`bg-accent-primary/15`,text:`text-accent-primary`,ring:`ring-accent-primary/40`,dot:`bg-accent-primary`}};function i(e,t){return new Proxy(e,{get(e,n,r){return typeof n==`string`&&!(n in e)?t(n):Reflect.get(e,n,r)}})}var a=i(Object.defineProperties({},Object.fromEntries(Object.keys(t).map(n=>[n,{get:()=>e.t(t[n]),enumerable:!0}]))),e=>e),o=i(r,()=>n);export{a as n,o as t};
@@ -0,0 +1 @@
1
+ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-zu2_FqZY.js";import{o as r,w as i}from"./i18n-DwgvwTnx.js";import{n as a}from"./dist-bVqfYmby.js";import{a as o}from"./auth-D_5bQv5W.js";var s=e(t(),1),c=null;function l(e){c=e}function u(){if(!c)throw Error(`getApiBase called with no active project — call setActiveProjectId first`);return`${o}/api/projects/${c}`}var d=new Map;function f(e,t){return`${e}:${t}`}function p(e){return(e instanceof Error?e.message:e==null?``:String(e)).trim()||`Request failed`}function m(e){if(!e)return;let t=`${e}:`;for(let e of d.keys())e.startsWith(t)&&d.delete(e)}function h({namespace:e,projectId:t,initialValue:n,fetcher:r,pollInterval:i=0}){let a=t?f(t,e):null,[o,c]=(0,s.useState)(()=>a&&d.has(a)?d.get(a):n),[l,u]=(0,s.useState)(()=>!a||!d.has(a)),[m,h]=(0,s.useState)(l),[g,_]=(0,s.useState)(null),v=(0,s.useRef)(r);v.current=r;let y=(0,s.useRef)(n);y.current=n;let b=(0,s.useRef)(a);b.current=a;let x=(0,s.useRef)(0),S=(0,s.useRef)(null),C=(0,s.useCallback)(async(e,t,n)=>{let r=++x.current;S.current?.abort();let i=new AbortController;S.current=i,n&&b.current===e&&(h(!0),_(null));try{let n=await v.current({projectId:t,signal:i.signal});if(i.signal.aborted||r!==x.current)return;d.set(e,n),b.current===e&&(c(n),_(null))}catch(t){if(i.signal.aborted||r!==x.current)return;b.current===e&&_(p(t))}finally{r===x.current&&(S.current===i&&(S.current=null),b.current===e&&(h(!1),u(!1)))}},[]);return(0,s.useEffect)(()=>{if(x.current+=1,S.current?.abort(),S.current=null,_(null),!a||!t){c(y.current),u(!0),h(!1);return}let e=d.has(a);e?(c(d.get(a)),u(!1),h(!1)):(c(y.current),u(!0),h(!0)),C(a,t,!e);let n;return i>0&&(n=setInterval(()=>{C(a,t,!1)},i)),()=>{x.current+=1,S.current?.abort(),S.current=null,n&&clearInterval(n)}},[a,t,i,C]),{data:o,isLoading:m,isFirstLoad:l,error:g,refresh:(0,s.useCallback)(async()=>{!a||!t||await C(a,t,!0)},[a,t,C])}}var g=n();function _(e){return e.providers&&e.providers.length>0?e.providers:[e.provider]}var v=(0,s.createContext)(null),y=`specrails-desktop:activeProjectId`;function b(e){try{e?localStorage.setItem(y,e):localStorage.removeItem(y)}catch{}}function x(){try{return localStorage.getItem(y)}catch{return null}}function S({children:e}){let[t,n]=(0,s.useState)([]),[c,u]=(0,s.useState)(null),[d,f]=(0,s.useState)(!0),[p,h]=(0,s.useState)(!1),_=(0,s.useRef)(null),y=(0,s.useRef)(new Set),S=(0,s.useCallback)(e=>{b(e),l(e),u(t=>(t!==null&&t!==e&&(_.current&&clearTimeout(_.current),h(!0),_.current=setTimeout(()=>h(!1),400)),e))},[]),{registerHandler:C,unregisterHandler:w}=i();(0,s.useEffect)(()=>{async function e(){try{let e=await fetch(`${o}/api/projects`);if(!e.ok)return;let t=await e.json();n(t.projects)}catch{}finally{f(!1)}}e()},[]);let T=(0,s.useCallback)(e=>{let t=e;if(typeof t.type==`string`){if(t.type===`desktop.projects`){if(!Array.isArray(t.projects))return;let e=t.projects;n(e),u(t=>{let n;if(t&&e.find(e=>e.id===t))n=t;else{let t=x();n=t&&e.find(e=>e.id===t)?t:e.length>0?e[0].id:null}return b(n),l(n),n}),f(!1)}else if(t.type===`desktop.project_added`){let e=t.project;n(t=>t.find(t=>t.id===e.id)?t:[...t,e]),y.current.has(e.id)?y.current.delete(e.id):(a.success(r.t(`nav:projects.added`,{name:e.name})),S(e.id))}else if(t.type===`providers.detected_changed`)fetch(`${o}/api/projects`).then(e=>e.ok?e.json():null).then(e=>{e&&Array.isArray(e.projects)&&n(e.projects)}).catch(()=>{});else if(t.type===`desktop.project_removed`){let e=t.projectId;a.success(r.t(`nav:projects.removed`)),m(e),n(t=>t.filter(t=>t.id!==e)),u(t=>t===e?(b(null),l(null),null):t)}}},[]);(0,s.useLayoutEffect)(()=>(C(`desktop`,T),()=>w(`desktop`)),[T,C,w]);let E=(0,s.useRef)(0);(0,s.useEffect)(()=>{let e=()=>{let e=Date.now();e-E.current<6e4||(E.current=e,fetch(`${o}/api/providers/detected?refresh=1`).catch(()=>{}))};return window.addEventListener(`focus`,e),()=>window.removeEventListener(`focus`,e)},[]);let D=(0,s.useCallback)(async(e,t,r)=>{try{let i={path:e};r&&r.length>0&&(i.providers=r),t&&(i.name=t);let a=await fetch(`/api/projects`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(i)});if(!a.ok){let e=await a.json();throw Error(e.error??`HTTP ${a.status}`)}let o=await a.json();return y.current.add(o.project.id),n(e=>e.find(e=>e.id===o.project.id)?e:[...e,o.project]),S(o.project.id),o}catch(e){throw console.error(`[useDesktop] addProject error:`,e),e}},[S]),O=(0,s.useCallback)(async e=>{try{let t=await fetch(`${o}/api/projects/${e}`,{method:`DELETE`});if(!t.ok){let e=await t.json();throw Error(e.error??`HTTP ${t.status}`)}m(e),n(t=>t.filter(t=>t.id!==e)),u(t=>t===e?(b(null),l(null),null):t)}catch(e){throw console.error(`[useDesktop] removeProject error:`,e),e}},[]),k=(0,s.useMemo)(()=>({projects:t,activeProjectId:c,setActiveProjectId:S,addProject:D,removeProject:O,isLoading:d,isSwitchingProject:p}),[t,c,S,D,O,d,p]);return(0,g.jsx)(v.Provider,{value:k,children:e})}var C={projects:[],activeProjectId:null,setActiveProjectId:()=>{},addProject:async()=>null,removeProject:async()=>{},isLoading:!1,isSwitchingProject:!1};function w(){return(0,s.useContext)(v)??C}export{u as a,h as i,_ as n,w as r,S as t};
@@ -204,81 +204,79 @@
204
204
  .specrails-splash__mark .pill { opacity: 1; }
205
205
  }
206
206
  </style>
207
- <script type="module" crossorigin src="/assets/index-C_sdsSZt.js"></script>
207
+ <script type="module" crossorigin src="/assets/index-DhjjFyU5.js"></script>
208
208
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CNC7AqOf.js">
209
209
  <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-zu2_FqZY.js">
210
210
  <link rel="modulepreload" crossorigin href="/assets/react-dom-DiWzBDP9.js">
211
211
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-Czpn1I53.js">
212
- <link rel="modulepreload" crossorigin href="/assets/chunk-KS7C4IRE-Cx5sqopX.js">
212
+ <link rel="modulepreload" crossorigin href="/assets/chunk-KS7C4IRE-DEKu087k.js">
213
213
  <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-Bjbbp_KS.js">
214
+ <link rel="modulepreload" crossorigin href="/assets/auth-D_5bQv5W.js">
214
215
  <link rel="modulepreload" crossorigin href="/assets/activity-DiyjsgQV.js">
215
216
  <link rel="modulepreload" crossorigin href="/assets/addspec-D2YIQ0GU.js">
216
- <link rel="modulepreload" crossorigin href="/assets/agent-mTCsAbCl.js">
217
+ <link rel="modulepreload" crossorigin href="/assets/agent-DB1JeE64.js">
217
218
  <link rel="modulepreload" crossorigin href="/assets/agents-ms7TwG16.js">
218
219
  <link rel="modulepreload" crossorigin href="/assets/agentstudio-CN2pokQu.js">
219
220
  <link rel="modulepreload" crossorigin href="/assets/aiedit-iIjz2_Qk.js">
220
221
  <link rel="modulepreload" crossorigin href="/assets/analytics-BMzCvrCU.js">
221
222
  <link rel="modulepreload" crossorigin href="/assets/attachments-B53PG733.js">
222
- <link rel="modulepreload" crossorigin href="/assets/browser-CXUcnTMI.js">
223
+ <link rel="modulepreload" crossorigin href="/assets/browser-Cu58EpZU.js">
223
224
  <link rel="modulepreload" crossorigin href="/assets/builder-CHuahItu.js">
224
225
  <link rel="modulepreload" crossorigin href="/assets/chat-BGD7MXAy.js">
225
226
  <link rel="modulepreload" crossorigin href="/assets/code-CHPkOkDS.js">
226
- <link rel="modulepreload" crossorigin href="/assets/commands-DyUZQ1FI.js">
227
+ <link rel="modulepreload" crossorigin href="/assets/commands-6tYQih5m.js">
227
228
  <link rel="modulepreload" crossorigin href="/assets/common-DHsv9N5d.js">
228
- <link rel="modulepreload" crossorigin href="/assets/dashboard-CTsM8x3m.js">
229
+ <link rel="modulepreload" crossorigin href="/assets/dashboard-B9GhC5ku.js">
229
230
  <link rel="modulepreload" crossorigin href="/assets/explore-DgBACn39.js">
230
231
  <link rel="modulepreload" crossorigin href="/assets/integrations-mB-7zoyT.js">
231
232
  <link rel="modulepreload" crossorigin href="/assets/jira-CKJg3HZd.js">
232
233
  <link rel="modulepreload" crossorigin href="/assets/jobs-B_TeSQxb.js">
233
- <link rel="modulepreload" crossorigin href="/assets/loops-CnxqL8IL.js">
234
+ <link rel="modulepreload" crossorigin href="/assets/loops-B7YpGizO.js">
234
235
  <link rel="modulepreload" crossorigin href="/assets/mcp-uDkXYu4L.js">
236
+ <link rel="modulepreload" crossorigin href="/assets/narration-DRaDnhT5.js">
235
237
  <link rel="modulepreload" crossorigin href="/assets/nav-DjkmQJjd.js">
236
- <link rel="modulepreload" crossorigin href="/assets/settings-CuVwOpe0.js">
238
+ <link rel="modulepreload" crossorigin href="/assets/packet-Bh5kkbiB.js">
239
+ <link rel="modulepreload" crossorigin href="/assets/settings-DHh7_IF5.js">
237
240
  <link rel="modulepreload" crossorigin href="/assets/setup-3jWaoVUO.js">
238
241
  <link rel="modulepreload" crossorigin href="/assets/specs-DII0C8gw.js">
239
242
  <link rel="modulepreload" crossorigin href="/assets/terminal-BFRZnMPP.js">
240
243
  <link rel="modulepreload" crossorigin href="/assets/tickets-W6pBmbrX.js">
241
- <link rel="modulepreload" crossorigin href="/assets/i18n-nJQvD5zJ.js">
242
- <link rel="modulepreload" crossorigin href="/assets/Trans-CTqw6rr9.js">
244
+ <link rel="modulepreload" crossorigin href="/assets/i18n-DwgvwTnx.js">
245
+ <link rel="modulepreload" crossorigin href="/assets/Trans-DXZ-BLXb.js">
243
246
  <link rel="modulepreload" crossorigin href="/assets/dist-bVqfYmby.js">
244
- <link rel="modulepreload" crossorigin href="/assets/arrow-left-CpIR4j7G.js">
245
- <link rel="modulepreload" crossorigin href="/assets/auth-D_5bQv5W.js">
246
- <link rel="modulepreload" crossorigin href="/assets/api-B2M7CtDw.js">
247
- <link rel="modulepreload" crossorigin href="/assets/useBrowserCaptureSession-BBXBN0Na.js">
248
- <link rel="modulepreload" crossorigin href="/assets/arrow-up-right-9DoMn7fD.js">
247
+ <link rel="modulepreload" crossorigin href="/assets/save-B6wGWwBR.js">
249
248
  <link rel="modulepreload" crossorigin href="/assets/sparkles-DrY8hb0X.js">
250
249
  <link rel="modulepreload" crossorigin href="/assets/x-CBMI9ejn.js">
251
- <link rel="modulepreload" crossorigin href="/assets/useSharedWebSocket-DK96ctb7.js">
252
- <link rel="modulepreload" crossorigin href="/assets/useDesktop-B6Du2N83.js">
253
- <link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-CHsu3NCX.js">
254
- <link rel="modulepreload" crossorigin href="/assets/external-link-BcU5EWZp.js">
255
- <link rel="modulepreload" crossorigin href="/assets/chevron-right-CViMxucY.js">
250
+ <link rel="modulepreload" crossorigin href="/assets/useDesktop-BzX769yH.js">
251
+ <link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-CcjBHMHd.js">
252
+ <link rel="modulepreload" crossorigin href="/assets/file-text-BcLCfcWZ.js">
256
253
  <link rel="modulepreload" crossorigin href="/assets/circle-x-CMYsN4ZH.js">
257
254
  <link rel="modulepreload" crossorigin href="/assets/download-CFHyyHcw.js">
258
- <link rel="modulepreload" crossorigin href="/assets/file-text-CFT-xXKN.js">
259
255
  <link rel="modulepreload" crossorigin href="/assets/git-branch-BRM0F5GN.js">
260
256
  <link rel="modulepreload" crossorigin href="/assets/loader-circle-DcygOQpC.js">
261
257
  <link rel="modulepreload" crossorigin href="/assets/pencil-C9RkrIPZ.js">
262
258
  <link rel="modulepreload" crossorigin href="/assets/play-DNlrawbS.js">
263
259
  <link rel="modulepreload" crossorigin href="/assets/plus-CsnjfkmX.js">
264
- <link rel="modulepreload" crossorigin href="/assets/save-28sIlCdr.js">
265
260
  <link rel="modulepreload" crossorigin href="/assets/search-Cq_gE3Ik.js">
266
261
  <link rel="modulepreload" crossorigin href="/assets/trash-2-BOU3WdZZ.js">
267
262
  <link rel="modulepreload" crossorigin href="/assets/triangle-alert-H9T4jAmh.js">
263
+ <link rel="modulepreload" crossorigin href="/assets/jira-api-CcZMcEN5.js">
268
264
  <link rel="modulepreload" crossorigin href="/assets/zap-CHo3aCD1.js">
269
- <link rel="modulepreload" crossorigin href="/assets/dist-Cez5CRkX.js">
270
265
  <link rel="modulepreload" crossorigin href="/assets/clsx-CjueKrWZ.js">
271
266
  <link rel="modulepreload" crossorigin href="/assets/utils-BC4DHlz2.js">
272
- <link rel="modulepreload" crossorigin href="/assets/button-BUIIBQPT.js">
273
- <link rel="modulepreload" crossorigin href="/assets/dialog-CzIekCw0.js">
274
- <link rel="modulepreload" crossorigin href="/assets/jira-api-Bhixwn-d.js">
267
+ <link rel="modulepreload" crossorigin href="/assets/dialog-CdWCb02j.js">
268
+ <link rel="modulepreload" crossorigin href="/assets/button-CJqFnUOL.js">
275
269
  <link rel="modulepreload" crossorigin href="/assets/provider-capabilities-Doej9lfa.js">
276
270
  <link rel="modulepreload" crossorigin href="/assets/lib-CGdyxIWg.js">
277
- <link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-EjGOpn4b.js">
278
- <link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-BOuXgebD.js">
279
- <link rel="modulepreload" crossorigin href="/assets/spending-BpYPsevQ.js">
280
- <link rel="modulepreload" crossorigin href="/assets/TicketDetailModal-MhJzrqbi.js">
281
- <link rel="stylesheet" crossorigin href="/assets/index-DCN5QbDm.css">
271
+ <link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-owhDdTXs.js">
272
+ <link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-C0ImhVKp.js">
273
+ <link rel="modulepreload" crossorigin href="/assets/spending-BQX3P7Cq.js">
274
+ <link rel="modulepreload" crossorigin href="/assets/TicketDetailModal-BvZoyDAL.js">
275
+ <link rel="modulepreload" crossorigin href="/assets/bot-DzuuuC5R.js">
276
+ <link rel="modulepreload" crossorigin href="/assets/git-refresh-wTW0rO0Z.js">
277
+ <link rel="modulepreload" crossorigin href="/assets/core-B3mKBIh5.js">
278
+ <link rel="modulepreload" crossorigin href="/assets/event-Bt7f3wXe.js">
279
+ <link rel="stylesheet" crossorigin href="/assets/index-xBFsOEiJ.css">
282
280
  </head>
283
281
  <body>
284
282
  <div id="specrails-splash" aria-hidden="false" role="status" aria-label="Loading Specrails">
@@ -0,0 +1,161 @@
1
+ # Embedded browser: native-webview evaluation (Cursor-class experience)
2
+
3
+ > Status: **implemented (phases 0–1 + devtools/zoom from phase 2)** — researched
4
+ > 2026-07-26, shipped via OpenSpec change `native-embedded-browser`. As-built:
5
+ > `src-tauri/src/browser.rs`, `client/src/lib/native-browser.ts`,
6
+ > `client/src/components/browser-capture/NativeBrowserPane.tsx`, router in
7
+ > `WebViewModal.tsx`. Open follow-ups: Windows z-order QA on a real build
8
+ > (tauri #9798), localhost-preview affordance (port detection), agent
9
+ > observability (phase 3).
10
+
11
+ ## 1. The problem
12
+
13
+ The current "embedded browser" is a **remote-rendered screencast**, not a browser:
14
+
15
+ ```
16
+ headless Chromium (Playwright, server) ──CDP Page.startScreencast──▶ JPEG q70 frames
17
+ ▲ │ WebSocket
18
+ │ every key/click/scroll/clipboard round-trips ▼
19
+ input WS ◀──────────────────────────────── <canvas> + createImageBitmap (client)
20
+ ```
21
+
22
+ - `server/browser-playwright.ts` — CDP screencast (JPEG, quality 70, `everyNthFrame: 1`).
23
+ - `server/browser-context-pool.ts` — one persistent headless Chromium resident for the app.
24
+ - `client/src/components/browser-capture/useBrowserCaptureSession.ts` — WS → bitmap → canvas pipeline, newest-frame-wins.
25
+ - The app **bundles a full Chromium** in the installer (`server/chromium-resolver.ts`, XOR blob `chromium.pak`) just to power this.
26
+
27
+ Consequences: a second full Chromium process tree resident; continuous JPEG
28
+ encode + WS transfer + decode + canvas paint; input latency on every keystroke;
29
+ scroll feels remote; text is lossy JPEG. And the SSRF guard (`isNavigableUrl`)
30
+ **blocks localhost/private IPs**, so the #1 IDE-browser use case — previewing your
31
+ own dev server — is impossible by design.
32
+
33
+ Surfaces using it today:
34
+ - `WebViewModal` (browse-only): links from `TicketDetailModal`, `AgentMessage`, `AgentPrDecisionCard`.
35
+ - `BrowserCaptureModal` ("Add Spec from a website"): element select, DOM capture,
36
+ annotations, responsive breakpoints, network sketch — this one has real
37
+ product value in the Playwright path.
38
+
39
+ ## 2. What the best IDEs actually do
40
+
41
+ | IDE | Mechanism | Verdict |
42
+ |---|---|---|
43
+ | **Cursor** (Browser / Browser control) | Real native Chromium view composited in-window (Electron `WebContentsView`-class, per the z-order-over-dialogs bug signature) + **CDP side-channel for the agent** (console, network, screenshots), element picker → chat, DevTools panes, localhost hot-reload | The bar to match |
44
+ | **JetBrains** | JCEF — real embedded Chromium in the runtime | Same tier |
45
+ | **VS Code** | Simple Browser / Live Preview = **iframe in webview** — breaks on `X-Frame-Options`/`frame-ancestors`; the old full-fidelity extension (Browser Preview) was a **CDP screencast and was deprecated** for exactly our symptoms | Cheap tier |
46
+ | **Windsurf** | Separate Chromium-fork window paired with the IDE + agent observability | Adjacent pattern |
47
+
48
+ Pattern: **nobody premium streams screenshots**. They composite a real native
49
+ browser surface into the window and keep the "chrome" (address bar, tabs) in the
50
+ editor's own UI. The 2025–26 differentiator is **agent observability** (console,
51
+ network, screenshot-to-chat), not tabs.
52
+
53
+ ## 3. Options for specrails-desktop (Tauri v2)
54
+
55
+ | Option | Fidelity | Effort | Risk | Notes |
56
+ |---|---|---|---|---|
57
+ | **A. Tauri child webview** (`unstable` feature, `Window::add_child` + `WebviewBuilder`) | Native (WKWebView / WebView2) | Medium | Medium — unstable flag, bug tail | The Electron-`WebContentsView` equivalent. Recommended. |
58
+ | B. Separate `WebviewWindow` | Native | Low | Low | Own OS window; docking is fake. Good fallback if A blocks. |
59
+ | C. CEF via `cef-rs` | Chromium-exact | Very high | High | Raw bindings, no plugin, +150 MB, signing/notarization project. Not now. |
60
+ | D. iframe in main webview | n/a | Low | — | Dead on arrival for arbitrary sites (frame-ancestors). |
61
+ | E. Keep screencast | Lossy | 0 | — | The thing we're replacing. Stays as fallback + capture engine. |
62
+
63
+ Key facts on **A** (verified against tauri 2.11.x / wry 0.55 docs + issue tracker):
64
+ - API available per-webview: `navigate`, `url`, `reload`, `eval`, `show/hide`,
65
+ `set_bounds`/`set_auto_resize`, `set_zoom`, `open_devtools`, cookies APIs,
66
+ `clear_all_browsing_data`, `incognito`, `data_directory`, `proxy_url`,
67
+ `user_agent`, `on_navigation` (cancelable), `on_page_load`, `on_new_window`
68
+ (popup policy), `on_download`.
69
+ - **No back/forward API** — portable route is `eval("history.back()")`; macOS can
70
+ reach the raw `WKWebView` (`WebViewExtMacOS`) for real `goBack/canGoBack` +
71
+ `with_back_forward_navigation_gestures` (swipe).
72
+ - **Hard constraint:** the child webview is a separate native surface — main-webview
73
+ HTML can never render above it. Same constraint Cursor lives with (their browser
74
+ overdraws their own dialogs — public bug). Mitigation: `hide()`/shrink the pane
75
+ whenever a modal/dropdown would intersect it.
76
+ - **Platform notes:** macOS + Windows supported (our only shipped targets — Linux
77
+ Wayland limitation is moot). Known unstable-flag bug tail to QA against:
78
+ z-order inconsistency (#9798), resize stalls (#10131), white-on-load (#10011),
79
+ only-last-child renders (#11376), example positioning (#10420).
80
+
81
+ ## 4. Recommended architecture
82
+
83
+ **Hybrid: native child webview for browsing; Playwright stays for capture; screencast stays as fallback.**
84
+
85
+ ```
86
+ ┌────────────────────────────── Tauri window ──────────────────────────────┐
87
+ │ main webview (React app) │
88
+ │ ┌─ BrowserPane chrome (React): address bar, back/fwd, reload, devtools ─┐│
89
+ │ │ ┌──────────────── reserved <div> hole ────────────────┐ ││
90
+ │ │ │ child webview (WKWebView / WebView2) │◀─ set_bounds ││
91
+ │ │ │ real page, native compositing, zero streaming │ from ││
92
+ │ │ └──────────────────────────────────────────────────────┘ ResizeObs. ││
93
+ │ └────────────────────────────────────────────────────────────────────────┘│
94
+ └───────────────────────────────────────────────────────────────────────────┘
95
+ ```
96
+
97
+ - **Rust side (`src-tauri`):** enable `features = ["unstable"]`; commands
98
+ `browser_open(url, bounds)`, `browser_set_bounds`, `browser_navigate`,
99
+ `browser_back/forward` (eval + macOS ext), `browser_hide/show`, `browser_close`,
100
+ `browser_devtools`. Events to JS: `browser://nav` (url/title from
101
+ `on_navigation`/`on_page_load` + title polling via eval), `browser://new-window`
102
+ (popup policy: open in-pane by default). One pane v1 (no tabs), `label`-addressed
103
+ so tabs are a v2, not a rewrite.
104
+ - **Client:** `BrowserPane.tsx` replaces `WebViewModal`'s canvas: renders only the
105
+ chrome + an empty measured `<div>`; `ResizeObserver` + scroll/layout effects
106
+ stream the rect over IPC. A tiny `useNativeBrowserOverlayGuard` hides the child
107
+ webview while any `z`-layered surface (Radix dialog, dropdown, toast area,
108
+ MinimizedChatsDock interactions) intersects the pane — the same discipline the
109
+ terminal panel already applies to its host div, inverted.
110
+ - **Session/profile:** `data_directory` under `~/.specrails/native-browser-profile/`
111
+ → persistent logins like a real browser (parallel to the Playwright profile, not
112
+ shared — WKWebView/WebView2 cannot read a Chromium profile). Optional incognito
113
+ flag per open.
114
+ - **localhost allowed.** The SSRF guard exists because the *server* renders pages;
115
+ a native client-side webview is just a browser the user drives — no server-side
116
+ fetch, no SSRF. This unlocks the killer feature: **dev-server preview** (open
117
+ `http://localhost:5173` next to the terminal panel, Cursor-style).
118
+ - **Fallback ladder (runtime-detected):**
119
+ 1. Tauri desktop → native child webview.
120
+ 2. `npm run dev` in a plain browser (no Tauri) → current screencast path, unchanged.
121
+ 3. Kill switch `SPECRAILS_NATIVE_BROWSER=false` / `VITE_FEATURE_NATIVE_BROWSER=false`
122
+ → screencast everywhere (byte-identical legacy).
123
+ - **Capture stays Playwright.** "Add Spec from a website" (element probe, DOM
124
+ capture, annotate, breakpoints, network ring) keeps the headless path — that is
125
+ an *instrumented agent browser*, the analogue of Cursor's CDP channel, and CDP
126
+ does not exist on WKWebView. Long-term option: move light capture (screenshot of
127
+ the native pane via injected JS/`print`) natively and drop the bundled Chromium
128
+ from the installer for users who never capture — out of scope v1.
129
+
130
+ ### Why not B/C first
131
+ B (separate window) loses the in-layout experience that defines Cursor's browser;
132
+ keep it as the contingency if the spike hits a blocker on Windows z-order. C (CEF)
133
+ is the only path to Chromium-exact + CDP-native embedding, but it is a
134
+ multi-month integration and ~150 MB — revisit only if agent-side observability on
135
+ the *interactive* pane becomes a hard requirement.
136
+
137
+ ## 5. Rollout plan
138
+
139
+ - **Phase 0 — spike (1 day):** branch; `unstable` feature on; hardcoded
140
+ `add_child` pane over the Dashboard; verify on macOS: bounds sync under sidebar
141
+ animation, hide-on-modal, focus in/out, `on_new_window`, localhost nav. Then the
142
+ same build on Windows (the z-order + white-on-load bugs are Windows-flavored).
143
+ Exit criteria: 60 fps scroll, no keystroke latency, no compositing artifacts.
144
+ - **Phase 1 — replace browse surfaces:** `BrowserPane` + Rust commands; route
145
+ `WebViewModal` callers (spec links, agent links, PR cards) through it; fallback
146
+ ladder + kill switches; i18n ×8; docs.
147
+ - **Phase 2 — premium chrome:** omnibox with history, zoom controls, per-pane
148
+ DevTools button, "Preview localhost" affordance (detect ports from the terminal
149
+ panel / rails dev servers), swipe gestures on macOS.
150
+ - **Phase 3 (deferred) — agent observability:** injected-script console/error
151
+ collector + screenshot-to-spec from the native pane (Cursor's differentiator),
152
+ evaluated against the existing Playwright capture channel.
153
+
154
+ ## 6. Sources
155
+
156
+ - Cursor browser docs/blog: cursor.com/docs/agent/tools/browser · cursor.com/blog/browser-visual-editor
157
+ - VS Code Simple Browser iframe limits: microsoft/vscode#127141 · deprecated screencast ext: auchenberg/vscode-browser-preview
158
+ - JetBrains JCEF: plugins.jetbrains.com/docs/intellij/embedded-browser-jcef.html
159
+ - Tauri: docs.rs/tauri `WebviewBuilder`/`Webview` · PR #8280, #9059 · issues #9798, #10420, #10131, #10011, #11376, #14588 · `examples/multiwebview`
160
+ - wry: docs.rs/wry `WebViewBuilder`, `WebViewExtMacOS`
161
+ - cef-rs: github.com/tauri-apps/cef-rs
@@ -0,0 +1,212 @@
1
+ # Review packet (as-built)
2
+
3
+ The plain-language review surface a non-technical person decides on. Fulfils the
4
+ open `safe-pr-workflow` requirement *"the builder reviews a plain-language
5
+ what-changed + proof bundle … SHALL NOT expose raw git vocabulary"*, which
6
+ shipped as a deferral in that change and is discharged here.
7
+
8
+ Change: `openspec/changes/nontech-review-experience` (Wave 1 evidence, Wave 2
9
+ packet, Wave 3 revisions + narration).
10
+
11
+ ## Why it exists
12
+
13
+ At `on_review` the app used to ask "Create PR / Integrate locally / Discard" —
14
+ git vocabulary, offered to someone who cannot read a diff. Nothing told them
15
+ what was done, whether it was checked, or what it cost. The packet answers those
16
+ three questions from data the app already has, and reduces the decision to three
17
+ verbs.
18
+
19
+ ## The honesty contract
20
+
21
+ This is the load-bearing design constraint, not a nicety. Three rules:
22
+
23
+ 1. **Every claim carries its source.** `server/review-packet.ts` splits proof
24
+ into three tiers and the tier travels with the item so the UI must label it:
25
+
26
+ | Tier | Source | UI framing |
27
+ |---|---|---|
28
+ | `app-verified` | Specrails measured it (diff stats, changed test files, commits recorded) | facts |
29
+ | `ai-reported` | the agent said so (`VERIFICATION: PASS` sentinel, verify output) | "the AI reports … Specrails did not run these checks itself" |
30
+ | `reviewer-score` | `sr-reviewer`'s own `confidence-score.json` | "the AI grading its own work — not an independent review" |
31
+
32
+ 2. **No numeric verification claim without a structured source.** There is no
33
+ structured test-count anywhere in the pipeline today (`loop_step_end.exitCode`
34
+ is `null` by contract for ai-steps; the PR body's Tests section is test FILE
35
+ paths). So the packet never prints "68 tests passed". Verify prose is carried
36
+ verbatim inside a labelled `rawExcerpt` block instead, and
37
+ `packetHasUnsourcedNumericClaim` guards the invariant in tests.
38
+
39
+ 3. **Absence renders as absence.** No reviewer score → "No reviewer score", not
40
+ silence that reads as approval. No test files touched → an explicit "No test
41
+ files were changed." Harvest failure → an `evidence unavailable` badge.
42
+
43
+ ## Data flow
44
+
45
+ ```
46
+ launch ──► rail_pr_deliveries INSERT
47
+ └─ spec_snapshot (migration 56): title/description/labels FROZEN
48
+ so "what you asked" cannot drift when the spec
49
+ is edited mid-run
50
+
51
+ settle ──► harvestDeliveryEvidence() ← BEFORE any releaseRailWorktrees call
52
+ ├─ VERIFICATION sentinel (regex precedent: rail-merge-orchestrator)
53
+ ├─ verify output tail (≤4 KB)
54
+ └─ confidence-score.json (read from the still-mounted worktree)
55
+ └─ settle_evidence (migration 56)
56
+
57
+ GET /rails/pr-deliveries/:id/packet
58
+ ├─ composeReviewPacket() pure, zero model calls, reproducible
59
+ └─ resolveAcceptCapability() git remote + `gh auth token`
60
+ ```
61
+
62
+ Nothing in the read path re-reads a mutable store, spawns a model, or writes.
63
+ Composing the same delivery twice returns an identical document.
64
+
65
+ ## Variants
66
+
67
+ Chosen from durable outcomes only (`selectVariant`), because the failure and
68
+ nothing-changed cases are exactly where a non-technical user is most abandoned:
69
+
70
+ - `success` — work is ready.
71
+ - `no-changes` — the first-class `no_changes` settle outcome. Never rendered by
72
+ a template that implies changes; offers acknowledge + revise, never discard.
73
+ - `partial` — "N of M ready", per-ticket sections from durable per-unit outcomes.
74
+ - `failed` — what was attempted and why it stopped, with one primary next action.
75
+
76
+ ## Decision verbs
77
+
78
+ `client/src/lib/packet-verbs.ts` maps the FULL delivery state space (12
79
+ decisions × 9 actions × orthogonal outcome/status axes ≈ 14 presentation states)
80
+ onto Accept / Request changes / Discard — and marks every state where that
81
+ reduction would lie as `fineControlOnly`. Those states (the recovery family, a
82
+ closed PR, a degraded draft) render the existing `RailPrDecisionStrip` controls
83
+ instead of a friendly relabel of git work. The module header carries the full
84
+ table; `resolvePacketVerbs` is total, defaulting to `fineControlOnly` rather
85
+ than guessing.
86
+
87
+ **Accept declares what it physically does.** `server/accept-ladder.ts`
88
+ pre-resolves the ladder from shipped probes (`git remote`, offline
89
+ `gh auth token`) and fails CLOSED to `merge-local`:
90
+
91
+ - remote + authenticated gh → Accept = create the PR (reviewable, reversible).
92
+ - otherwise → Accept = write into the user's own checkout, and the page ALWAYS
93
+ interposes a plain-language consequence confirm first ("no separate review
94
+ copy … cannot be undone").
95
+
96
+ Decisions execute through the existing `useRailPrDecisions().act()` +
97
+ `rail.pr_state` broadcast + authoritative POST-response snapshot. The packet
98
+ holds no optimistic state; a raced answer surfaces the neutral
99
+ "someone already answered this".
100
+
101
+ ## Surfaces
102
+
103
+ - Routed page `/review/:prDeliveryId` (`client/src/pages/ReviewPacketPage.tsx`) —
104
+ chosen over a portal so the documented z-order ladder is irrelevant.
105
+ - Entry points: a `Review` button on the rail strip's `on_review` row and on the
106
+ agent-chat PR card. Both keep their precise git actions.
107
+ - Layout is an inverted pyramid: verdict + confidence + cost + verbs above the
108
+ fold; the sections are progressive disclosure below.
109
+ - "Discuss this delivery" deliberately routes into the existing agent chat
110
+ rather than adding a second Q&A brain (see the change's design D5).
111
+
112
+ ## Revisions (Wave 3)
113
+
114
+ "Ask for changes" is a sentence, not a new commission.
115
+
116
+ **The launch door.** `prDeliveryRevisionAllowed` (`rails-router.ts`) is the ONE
117
+ exemption to the pending-decision 409, and it is deliberately strict: the launch
118
+ must name the rail's ACTIVE generation, that generation must be non-terminal, and
119
+ the rail must still cover its exact ticket set. Anything else keeps the existing
120
+ 409; a wrong id gets a distinct `invalid_revision_target` so the caller learns
121
+ what to fix. `launch_all` deliberately does NOT get the exemption — a batch
122
+ fan-out must never silently revise.
123
+
124
+ **Revisions are generations, not a new state.** A revision creates a NEW delivery
125
+ row that supersedes the one it revises (migration 57 adds `revision_note` +
126
+ `revision_of`). No new `decision` value exists, because an unknown decision makes
127
+ older clients drop the whole PR card — and supersession already means "a newer
128
+ attempt replaced the previous one". Version lineage, crash-restore and
129
+ one-active-generation-per-rail all come free from the shipped machinery.
130
+
131
+ **The loop.** `factory:revision` is the fourth factory loop: `{{cmd:revise}}` (a
132
+ distilled step that applies the one requested change on top of the branch and
133
+ then runs `sr-reviewer` over the resulting diff, so packet v2 keeps its reviewer
134
+ tier) followed by the standard `{{cmd:verify}}`. No Architect step — the plan and
135
+ the code already exist. It is resolvable by id but deliberately NOT listed in the
136
+ public catalog: its prompt consumes `{{const:REVISION_REQUEST}}`, which only a
137
+ revision launch injects. The router forces this loop for any revision whatever
138
+ the rail's stored mode is; without that, every tweak would re-run implement.
139
+
140
+ **Fresh session by contract** (`server/revision-seed.ts`). The run is seeded from
141
+ durable state — the instruction, the frozen spec, the branch that carries the
142
+ work, and what the previous run actually REPORTED (failure first). `--resume` of
143
+ the prior session is not attempted: sessions are cwd-scoped to a released
144
+ worktree, `jobs.session_id` holds the final step's session rather than the
145
+ developer's, and every shipped recovery path in this codebase already prefers
146
+ fresh + durable. The branch carries the work; the seed carries the intent.
147
+
148
+ **Drift nudges, not limits.** `computeDriftNudges` fires on real measurements —
149
+ cumulative revision cost above half the original build, churn landing mostly
150
+ outside the original file set, or a revision-count backstop — and the banner
151
+ shows its own numbers. It never blocks another revision. A bare count was
152
+ rejected: it treats a 40-cent typo fix and a nine-dollar thrash identically.
153
+
154
+ ## Known limitations
155
+
156
+ - **Batch attribution.** `file_story_contributions` keys files to a run's
157
+ primary ticket, so when one run covers several tickets the per-section churn is
158
+ `null` and the delivery-level total is shown instead. Splitting it would invent
159
+ precision.
160
+ - **No revision duration estimate.** Nothing is promised until real
161
+ revision-run history exists (the honest-ranges rule applies to our own
162
+ marketing numbers too).
163
+ - **No structured test counts** until the parallel specrails-core ask lands
164
+ (counts emitted into `confidence-score.json`). When it does, the composer's
165
+ tier-1 branch picks them up and the claim becomes app-verified.
166
+
167
+ ## Narrated progress (Wave 3b)
168
+
169
+ The third altitude on the job log surfaces, between the glance-level phase chips
170
+ and the raw log. `client/src/components/loop-log/narration-model.ts`
171
+ `buildNarration` turns the persisted event stream into MILESTONES:
172
+
173
+ - Each milestone is a stable i18n key plus factual values — nothing in the model
174
+ writes prose, so nothing in it can invent. Zero model calls.
175
+ - The only outcomes stated are STRUCTURAL: a step's ok/failed status, a shell
176
+ exit code, the Loop Decider's routed verdict. Agent prose is never promoted —
177
+ "68 tests passed" in the stream produces no milestone and no number.
178
+ - Repeated identical activity collapses into a count, so a loop that reads the
179
+ same file nine times reads as nine attempts rather than nine lines.
180
+ - A step with no end event is only "interrupted" once the run has SETTLED; while
181
+ it is live it is simply still working.
182
+ - Silence is honest: an unrecognised frame produces no milestone rather than a
183
+ reassuring filler line.
184
+
185
+ **Provider degradation is predictable** because the model reuses
186
+ `deriveFrameActivity` — the same derivation the job status panel and rail metrics
187
+ use. Claude yields tool-level detail; codex/gemini/kimi yield fewer activity
188
+ milestones while every structural milestone stays identical.
189
+
190
+ **Waiting is honest.** The band comes from the Wave 1 percentile endpoint and is
191
+ rendered only when the server returned one (≥5 samples); below that, nothing is
192
+ shown rather than a guess. Elapsed time is real clock data. No estimate is ever
193
+ synthesised client-side.
194
+
195
+ **The toggle** follows the Code explorer's Story|Log precedent and DEFAULTS to
196
+ narrated, on both the routed Job Detail page and the mission-mode modal. The
197
+ preference is one app-level key (`client/src/lib/job-log-mode.ts`) rather than
198
+ per-project: it describes the reader, not a project, and splitting it made the
199
+ two surfaces disagree about what the same person had chosen. With
200
+ `VITE_FEATURE_NARRATED_PROGRESS=false` both surfaces render byte-identically to
201
+ their pre-change behaviour, with no toggle at all.
202
+
203
+ ## Flags
204
+
205
+ - `SPECRAILS_REVIEW_PACKET=false` → the packet route 404s.
206
+ - `VITE_FEATURE_REVIEW_PACKET=false` → the route and both entry points vanish.
207
+ - `SPECRAILS_DELIVERY_REVISIONS=false` → the revision exemption disappears and
208
+ every launch against an undecided delivery 409s exactly as before.
209
+ - `VITE_FEATURE_NARRATED_PROGRESS=false` → the job log surfaces lose the mode
210
+ toggle and render exactly as before.
211
+
212
+ Any flag off leaves the existing decision strip byte-identical.