diffing 0.18.1 → 0.18.3

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 (186) hide show
  1. package/dist/{cli-agent-Cf6SAnH4.mjs → cli-agent-DV5ZhMX1.mjs} +209 -25
  2. package/dist/{cli-gh-2NEumKQB.mjs → cli-gh-0hY7pvwJ.mjs} +2 -2
  3. package/dist/cli.mjs +13 -12
  4. package/dist/client/assets/{angular-html-BV3gPi7L.js → angular-html-DgLkj6cM.js} +1 -1
  5. package/dist/client/assets/{angular-ts-DuuSfKJK.js → angular-ts-S7IdfqOh.js} +1 -1
  6. package/dist/client/assets/{apl-BMc69egk.js → apl-dC851k0e.js} +1 -1
  7. package/dist/client/assets/{arc-CEC1cz1S.js → arc-v8u_6qG_.js} +1 -1
  8. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BmJ5OHgd.js → architectureDiagram-3BPJPVTR-CtfNNxPZ.js} +1 -1
  9. package/dist/client/assets/{astro-Bg_CYwmB.js → astro-DgnNzfoZ.js} +1 -1
  10. package/dist/client/assets/{blade-Mca0XoRC.js → blade-By7nZeyV.js} +1 -1
  11. package/dist/client/assets/{blockDiagram-GPEHLZMM--XrThRLW.js → blockDiagram-GPEHLZMM-cfmYosKW.js} +1 -1
  12. package/dist/client/assets/{c-B2Uqi6oI.js → c-C6I1CPq_.js} +1 -1
  13. package/dist/client/assets/{c4Diagram-AAUBKEIU-W8w5oiRi.js → c4Diagram-AAUBKEIU-L52oIoFq.js} +1 -1
  14. package/dist/client/assets/channel-B8Y5aSHX.js +1 -0
  15. package/dist/client/assets/{chunk-2J33WTMH-Bpr3MqKf.js → chunk-2J33WTMH-Dpy-98nI.js} +1 -1
  16. package/dist/client/assets/{chunk-3OPIFGDE-DdMV_3DM.js → chunk-3OPIFGDE-DsFmltuQ.js} +1 -1
  17. package/dist/client/assets/{chunk-4BX2VUAB-BkfeLLsz.js → chunk-4BX2VUAB-B1PXeD30.js} +1 -1
  18. package/dist/client/assets/{chunk-55IACEB6-pCwXbxR0.js → chunk-55IACEB6-BkfaPY5R.js} +1 -1
  19. package/dist/client/assets/{chunk-5ZQYHXKU-BGFzCgvs.js → chunk-5ZQYHXKU-kHj4q0HC.js} +1 -1
  20. package/dist/client/assets/{chunk-727SXJPM-DU7oVWfJ.js → chunk-727SXJPM-DYndNEF7.js} +1 -1
  21. package/dist/client/assets/{chunk-AQP2D5EJ-DaVYG1qm.js → chunk-AQP2D5EJ-CGgu3cJd.js} +1 -1
  22. package/dist/client/assets/{chunk-BSJP7CBP-DAvnzgjY.js → chunk-BSJP7CBP-B85q9YnN.js} +1 -1
  23. package/dist/client/assets/{chunk-CSCIHK7Q-D6ZDvMsi.js → chunk-CSCIHK7Q-D4izdgab.js} +1 -1
  24. package/dist/client/assets/{chunk-FMBD7UC4-BFDCYCqF.js → chunk-FMBD7UC4-CwH6kGrM.js} +1 -1
  25. package/dist/client/assets/{chunk-KSCS5N6A-BmD6XY52.js → chunk-KSCS5N6A-D6WNG6Qv.js} +1 -1
  26. package/dist/client/assets/{chunk-L5ZTLDWV-Dl9iUIBf.js → chunk-L5ZTLDWV-BFzgANjL.js} +1 -1
  27. package/dist/client/assets/{chunk-LZXEDZCA-rOOJDxdB.js → chunk-LZXEDZCA-CXVdeXQA.js} +2 -2
  28. package/dist/client/assets/{chunk-ND2GUHAM-DBDu-JeD.js → chunk-ND2GUHAM-nez7hw5f.js} +1 -1
  29. package/dist/client/assets/{chunk-NZK2D7GU-CRyk4F3R.js → chunk-NZK2D7GU-DQxrGm1r.js} +1 -1
  30. package/dist/client/assets/{chunk-O5CBEL6O-DcKB07iP.js → chunk-O5CBEL6O-BAYDbzO8.js} +1 -1
  31. package/dist/client/assets/chunk-QZHKN3VN-DPZNzASS.js +1 -0
  32. package/dist/client/assets/chunk-WU5MYG2G-DOKX6dPU.js +1 -0
  33. package/dist/client/assets/{chunk-XPW4576I-WuzWMSrp.js → chunk-XPW4576I-DdtioiQK.js} +1 -1
  34. package/dist/client/assets/classDiagram-4FO5ZUOK-CkitIGos.js +1 -0
  35. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BD7zcbFa.js +1 -0
  36. package/dist/client/assets/{cobol-oQ1i-YtO.js → cobol-DVRUz845.js} +1 -1
  37. package/dist/client/assets/{coffee-s9XlmHbB.js → coffee-ywp-ztcj.js} +1 -1
  38. package/dist/client/assets/{cose-bilkent-S5V4N54A-CymUm7fn.js → cose-bilkent-S5V4N54A-YAV-XlD-.js} +1 -1
  39. package/dist/client/assets/{cpp-D60LxTox.js → cpp-lGJg5yP8.js} +1 -1
  40. package/dist/client/assets/{crystal-CHrCczDb.js → crystal-C255DQNq.js} +1 -1
  41. package/dist/client/assets/{css-1PQO32Hw.js → css-D0fueLBG.js} +1 -1
  42. package/dist/client/assets/{dagre-BM42HDAG-DOHZc0tO.js → dagre-BM42HDAG-C-nRRJ_h.js} +1 -1
  43. package/dist/client/assets/{diagram-2AECGRRQ-D8O2cleS.js → diagram-2AECGRRQ-Cy08G2GT.js} +1 -1
  44. package/dist/client/assets/{diagram-5GNKFQAL-CO-Ej4UD.js → diagram-5GNKFQAL-B5KxlMfb.js} +1 -1
  45. package/dist/client/assets/{diagram-KO2AKTUF-DCUr4wzS.js → diagram-KO2AKTUF-BtlklsuV.js} +1 -1
  46. package/dist/client/assets/{diagram-LMA3HP47-D9HnA0Qg.js → diagram-LMA3HP47-DwJhvsVf.js} +1 -1
  47. package/dist/client/assets/{diagram-OG6HWLK6-BxAitO2n.js → diagram-OG6HWLK6-e6mkflzE.js} +1 -1
  48. package/dist/client/assets/{dist-N-ROBnkL.js → dist-8nkFH_K6.js} +1 -1
  49. package/dist/client/assets/{edge-Ck54Fn7y.js → edge-Cy4Xn6qQ.js} +1 -1
  50. package/dist/client/assets/{edit-B8RwtrDs.js → edit-C3wvBNC7.js} +1 -1
  51. package/dist/client/assets/{elixir-xXxdfsfU.js → elixir-CHz66mgQ.js} +1 -1
  52. package/dist/client/assets/{elm-g0vLRWbF.js → elm-CuSKJX5P.js} +1 -1
  53. package/dist/client/assets/{erDiagram-TEJ5UH35-1ZMg7g98.js → erDiagram-TEJ5UH35-U7lAAznz.js} +1 -1
  54. package/dist/client/assets/{erb-CGFnxOST.js → erb-BbtQ1hep.js} +1 -1
  55. package/dist/client/assets/{flowDiagram-I6XJVG4X-D5u9lhJ-.js → flowDiagram-I6XJVG4X-jIylSBs9.js} +1 -1
  56. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BbVKEzs8.js → ganttDiagram-6RSMTGT7-DW3d8a36.js} +1 -1
  57. package/dist/client/assets/{git-rebase-B3s6bE5_.js → git-rebase-R-XkmJUY.js} +1 -1
  58. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-sAottiiz.js → gitGraphDiagram-PVQCEYII-BHzkJu8s.js} +1 -1
  59. package/dist/client/assets/{glimmer-js-C2Xff18_.js → glimmer-js-C_yhese4.js} +1 -1
  60. package/dist/client/assets/{glimmer-ts-CRBS8ftd.js → glimmer-ts-DKD-Bn0M.js} +1 -1
  61. package/dist/client/assets/{glsl-aOY_t6dG.js → glsl-CJScdeHB.js} +1 -1
  62. package/dist/client/assets/{graphql-Cucpdpc6.js → graphql-BUP7lnbX.js} +1 -1
  63. package/dist/client/assets/{hack-Bw0KkVza.js → hack-Uu2tSM5_.js} +1 -1
  64. package/dist/client/assets/{haml-DnyaPoa2.js → haml-BKKDeeb3.js} +1 -1
  65. package/dist/client/assets/{handlebars-AedEof-m.js → handlebars-DXZJVvuj.js} +1 -1
  66. package/dist/client/assets/{html-Bu5h_Kgf.js → html-BNpfcyGi.js} +1 -1
  67. package/dist/client/assets/{html-derivative-CNiVLld1.js → html-derivative-D2Zj9RHU.js} +1 -1
  68. package/dist/client/assets/{http-B9-oOYPw.js → http-BDjsRgLN.js} +1 -1
  69. package/dist/client/assets/{hurl-Bo6NUnnd.js → hurl-D_YxHbnH.js} +1 -1
  70. package/dist/client/assets/{index-BkwV-g6J.js → index-CpN9wq1L.js} +142 -142
  71. package/dist/client/assets/{index-C73yr1tm.css → index-EoTRSuBL.css} +1 -1
  72. package/dist/client/assets/{infoDiagram-5YYISTIA-CfVELF4R.js → infoDiagram-5YYISTIA-oGpzG0Af.js} +1 -1
  73. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DztjIGfb.js → ishikawaDiagram-YF4QCWOH-066RGSaQ.js} +1 -1
  74. package/dist/client/assets/{java-JJ3dl_jz.js → java-DeOKsdfJ.js} +1 -1
  75. package/dist/client/assets/{javascript-QgUuQNTw.js → javascript-oM5JhL2b.js} +1 -1
  76. package/dist/client/assets/{jinja-GbeEm2-u.js → jinja-CpW87uSV.js} +1 -1
  77. package/dist/client/assets/{jison-BXZbHsO3.js → jison-Du6rTe9r.js} +1 -1
  78. package/dist/client/assets/{journeyDiagram-JHISSGLW-Lg8CkxeI.js → journeyDiagram-JHISSGLW-BB4Aymt4.js} +1 -1
  79. package/dist/client/assets/{json-D02YvQSa.js → json-Cl5vEJGM.js} +1 -1
  80. package/dist/client/assets/{jsx-eZde6NZS.js → jsx-D3lCkT_f.js} +1 -1
  81. package/dist/client/assets/{julia-CfcuUD-A.js → julia-P1bWdzXQ.js} +1 -1
  82. package/dist/client/assets/{just-hQr8WcQp.js → just-BQHIj4wn.js} +1 -1
  83. package/dist/client/assets/{kanban-definition-UN3LZRKU-BWIeZIf6.js → kanban-definition-UN3LZRKU-CveGMVSw.js} +1 -1
  84. package/dist/client/assets/{latex-BxffZwv_.js → latex-CzeBoguz.js} +1 -1
  85. package/dist/client/assets/{line-Cy1AFxf0.js → line-CjLRf0wl.js} +1 -1
  86. package/dist/client/assets/{linear-Kk5IbGHr.js → linear-GmrB0HMZ.js} +1 -1
  87. package/dist/client/assets/{liquid-BvKnpUyR.js → liquid-BNh96Tca.js} +1 -1
  88. package/dist/client/assets/{lua-Bcds2vY9.js → lua-CNQoqqQP.js} +1 -1
  89. package/dist/client/assets/{marko-CZJQLjYc.js → marko-C_xevFIb.js} +1 -1
  90. package/dist/client/assets/{mdc-BRigtixg.js → mdc-XPBTv6ak.js} +1 -1
  91. package/dist/client/assets/{mermaid-parser.core-Dh2uTVxt.js → mermaid-parser.core-HsXe3oD9.js} +1 -1
  92. package/dist/client/assets/{mermaid.core-CXIGVQ74.js → mermaid.core-BukRjtV2.js} +3 -3
  93. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DQNQJuAB.js → mindmap-definition-RKZ34NQL-VTmEFYO4.js} +1 -1
  94. package/dist/client/assets/{nginx-BI3-LI9U.js → nginx-C6cZlyey.js} +1 -1
  95. package/dist/client/assets/{nim-PEd8ZOME.js → nim-Cd9xainG.js} +1 -1
  96. package/dist/client/assets/{perl-BPTGdP4p.js → perl-DUfKG3XX.js} +1 -1
  97. package/dist/client/assets/{php-C8BKL0yL.js → php-DqMFPxyM.js} +1 -1
  98. package/dist/client/assets/{pieDiagram-4H26LBE5-Dz7gE0t5.js → pieDiagram-4H26LBE5-Bwlobw-Z.js} +1 -1
  99. package/dist/client/assets/{pug-H5z9rtbz.js → pug-GUix1iYH.js} +1 -1
  100. package/dist/client/assets/{qml-EzSpo2aM.js → qml-CqF34de7.js} +1 -1
  101. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-pTnmYdEJ.js → quadrantDiagram-W4KKPZXB-CMujQAS2.js} +1 -1
  102. package/dist/client/assets/{r-VMeUwMIt.js → r-BxeiO8R8.js} +1 -1
  103. package/dist/client/assets/{razor-Ne05zcaj.js → razor-Cu8elnMU.js} +1 -1
  104. package/dist/client/assets/{regexp-i5_jUZAj.js → regexp-DxNPsA-x.js} +1 -1
  105. package/dist/client/assets/{requirementDiagram-4Y6WPE33-GVw3NOxE.js → requirementDiagram-4Y6WPE33-CrrnxwFz.js} +1 -1
  106. package/dist/client/assets/{review-export-B4TOUDUE.js → review-export-Bd6rUYIi.js} +1 -1
  107. package/dist/client/assets/{rst-BZWuei_6.js → rst-2Xzw7kAb.js} +1 -1
  108. package/dist/client/assets/{ruby-CApICFTD.js → ruby-tZt1Nxb8.js} +1 -1
  109. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CwWzwmqj.js → sankeyDiagram-5OEKKPKP-ihur8vgn.js} +1 -1
  110. package/dist/client/assets/{sas-BzEPX6GL.js → sas-DjpUVtXs.js} +1 -1
  111. package/dist/client/assets/{scss-B6Ld2NRm.js → scss-CZffX3V1.js} +1 -1
  112. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bz_Q76Kt.js → sequenceDiagram-3UESZ5HK-Bg7fvZIu.js} +1 -1
  113. package/dist/client/assets/{shellscript-KmoqGVVu.js → shellscript-B7bmiOyR.js} +1 -1
  114. package/dist/client/assets/{shellsession-CsSh_KTo.js → shellsession-CS7D1mSi.js} +1 -1
  115. package/dist/client/assets/{soy-CLgOu1If.js → soy-B9Nii6ks.js} +1 -1
  116. package/dist/client/assets/{sql-BdCh56kU.js → sql-CzQPwj1z.js} +1 -1
  117. package/dist/client/assets/{src-D7QQkGEc.js → src-DNsvZq9f.js} +1 -1
  118. package/dist/client/assets/{stata-C3fo8EYS.js → stata-BjQgQlmT.js} +1 -1
  119. package/dist/client/assets/{stateDiagram-AJRCARHV-D0pZtzLu.js → stateDiagram-AJRCARHV-DsgTVCaj.js} +1 -1
  120. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CwRm1eh7.js +1 -0
  121. package/dist/client/assets/{surrealql-qzihsJw_.js → surrealql-D6DnLnn1.js} +1 -1
  122. package/dist/client/assets/{svelte-B4tbn_KI.js → svelte-DRgP3jXp.js} +1 -1
  123. package/dist/client/assets/{templ-Ddev3BvY.js → templ-Cwgx1vj-.js} +1 -1
  124. package/dist/client/assets/{tex-DJLtgNQ6.js → tex-RzUmM9ei.js} +1 -1
  125. package/dist/client/assets/{timeline-definition-PNZ67QCA-CWTWgYi0.js → timeline-definition-PNZ67QCA-rG2Mo1Zq.js} +1 -1
  126. package/dist/client/assets/{ts-tags-qqxigRtr.js → ts-tags-gdlLe8LD.js} +1 -1
  127. package/dist/client/assets/{tsx-Pkf9BaPv.js → tsx-C3y6O1l0.js} +1 -1
  128. package/dist/client/assets/{twig-BiScb10p.js → twig-DLMzUdS6.js} +1 -1
  129. package/dist/client/assets/{typescript-Bhy3UXig.js → typescript-T8PJwqaO.js} +1 -1
  130. package/dist/client/assets/{vennDiagram-CIIHVFJN-DwB89v9I.js → vennDiagram-CIIHVFJN-B-eDjTJG.js} +1 -1
  131. package/dist/client/assets/{vue-CANMWBRv.js → vue-BMIaruOV.js} +1 -1
  132. package/dist/client/assets/{vue-html-DtS6f62r.js → vue-html-D4e4SuHW.js} +1 -1
  133. package/dist/client/assets/{vue-vine-BpQ9kwSB.js → vue-vine-DU2nprLA.js} +1 -1
  134. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DNwwPk4s.js → wardleyDiagram-YWT4CUSO-BkcHoefB.js} +1 -1
  135. package/dist/client/assets/{xml-CD5eIXsm.js → xml-DD7circ2.js} +1 -1
  136. package/dist/client/assets/{xsl-DGNZQAUu.js → xsl-Bu-TQ7Ag.js} +1 -1
  137. package/dist/client/assets/{xychartDiagram-2RQKCTM6-L7M4YSvw.js → xychartDiagram-2RQKCTM6-BGa1-yRO.js} +1 -1
  138. package/dist/client/assets/{yaml-Cm8mwIB7.js → yaml-C12ZIXNt.js} +1 -1
  139. package/dist/client/index.html +2 -2
  140. package/dist/completions-C0ERX2wC.mjs +2 -0
  141. package/dist/design-system-types-QR9xS7o3.mjs +2 -0
  142. package/dist/design-system-types-uiP9W2uj.mjs +75 -0
  143. package/dist/{doctor-BxTaLqAF.mjs → doctor-Dg61wsBc.mjs} +6 -6
  144. package/dist/{doctor-BdDB6p27.mjs → doctor-Y8l1LEDM.mjs} +1 -1
  145. package/dist/{github-CToLsvPu.mjs → github-BtABlwOZ.mjs} +3 -3
  146. package/dist/{github--rpPZ3vK.mjs → github-BuBcy-co.mjs} +1 -1
  147. package/dist/{github-attachments-DEvAeo5P.mjs → github-attachments-Bwr25ItV.mjs} +1 -1
  148. package/dist/{mcp-DoNBucix.mjs → mcp-45VPdWIr.mjs} +136 -17
  149. package/dist/{mockup-format-D4Ln0WxJ.mjs → mockup-lint-BRjhxXSw.mjs} +160 -7
  150. package/dist/native/tui-darwin-arm64/diffing-tui +0 -0
  151. package/dist/native/tui-darwin-x64/diffing-tui +0 -0
  152. package/dist/native/tui-linux-arm64-gnu/diffing-tui +0 -0
  153. package/dist/native/tui-linux-arm64-musl/diffing-tui +0 -0
  154. package/dist/native/tui-linux-x64-gnu/diffing-tui +0 -0
  155. package/dist/native/tui-linux-x64-musl/diffing-tui +0 -0
  156. package/dist/native/tui-win32-x64-msvc/diffing-tui.exe +0 -0
  157. package/dist/{review-export-DoST2oPt.mjs → review-export-BXCeRNjk.mjs} +1 -1
  158. package/dist/{server-BRQdb64n.mjs → server-CH-UPGFR.mjs} +1145 -127
  159. package/dist/{server-lock-CdaUNZyX.mjs → server-lock-ACw2cKDI.mjs} +22 -5
  160. package/dist/{session-conflict-BVgyyerQ.mjs → session-conflict-oUPEv3YT.mjs} +3 -3
  161. package/dist/{session-manager-CUqF8hkP.mjs → session-manager-C9KjVkzq.mjs} +2 -2
  162. package/dist/{session-url-B9FOnF3F.mjs → session-url-CyBpcLAH.mjs} +1 -1
  163. package/dist/{settings-DkPDB_hS.mjs → settings-DIa-3C8o.mjs} +1 -1
  164. package/dist/{setup-BwWZ9Fzq.mjs → setup-B6bOeWQl.mjs} +6 -6
  165. package/dist/{setup-first-run-i1o7DBki.mjs → setup-first-run-CAjtqBal.mjs} +2 -2
  166. package/dist/{setup-first-run-DyT0PlOZ.mjs → setup-first-run-DPXLAMqa.mjs} +1 -1
  167. package/extensions/pi/index.ts +128 -60
  168. package/extensions/pi/skill-heal.ts +73 -0
  169. package/extensions/pi/tsconfig.json +2 -1
  170. package/package.json +1 -1
  171. package/dist/client/assets/channel-C9URWRe4.js +0 -1
  172. package/dist/client/assets/chunk-QZHKN3VN-BBNO2-yv.js +0 -1
  173. package/dist/client/assets/chunk-WU5MYG2G-HP2Om5f9.js +0 -1
  174. package/dist/client/assets/classDiagram-4FO5ZUOK-BW_ZPHe7.js +0 -1
  175. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOd628kk.js +0 -1
  176. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-P0vTqsyH.js +0 -1
  177. package/dist/completions-CYa5usLb.mjs +0 -2
  178. /package/dist/{apply-suggestion-BsR_8FXW.mjs → apply-suggestion-tLsWREyb.mjs} +0 -0
  179. /package/dist/{comment-format-D4mCqr7T.mjs → comment-format-DS1wOWj_.mjs} +0 -0
  180. /package/dist/{completions-2aHVTmiJ.mjs → completions-ClqvbDYF.mjs} +0 -0
  181. /package/dist/{diff-fingerprint-BL5jNHnP.mjs → diff-fingerprint-PnC8Fs1-.mjs} +0 -0
  182. /package/dist/{find-tui-binary-DGspwyvd.mjs → find-tui-binary-DvADkBkP.mjs} +0 -0
  183. /package/dist/{handoff-DcShCxWJ.mjs → handoff-B85WLpcN.mjs} +0 -0
  184. /package/dist/{terminal-BroplOnw.mjs → terminal-Ce7OL1A-.mjs} +0 -0
  185. /package/dist/{tui-search-bridge-fFKqX4Fi.mjs → tui-search-bridge-N33QmB0q.mjs} +0 -0
  186. /package/dist/{update-check-ISaw61qS.mjs → update-check-D0yDl7sZ.mjs} +0 -0
@@ -1,18 +1,19 @@
1
1
  import { C as listRepoFiles, D as toSafeRelativePath, E as isSafePath, S as isGitRepo, T as revertHunk, _ as getShowDiff, a as getCommitSeriesSummary, b as getUntrackedFilePathsAsync, d as getMergeStatus, f as getProjectStorageDir, h as getRepoRoot, l as getGitDiffAsync, m as getRepoMetadataAsync, o as getCustomGitDiffAsync, s as getFileContent, u as getHunkHistory, x as gitAddFile, y as getTabSizeForFilesAsync } from "./git-BGj31GX-.mjs";
2
2
  import { a as searchFiles, i as searchContent, o as searchSymbols, r as searchAll, s as trackSelection, t as getSearchStatus } from "./search-3R2E5xxr.mjs";
3
- import { i as loadSettings, s as saveSettings } from "./settings-DkPDB_hS.mjs";
4
- import { a as MOCKUP_MAX_SCREEN_BYTES, c as defaultScreenLabel, d as normalizeSubmitScreens, i as sectionTitleForLine, l as isMockupViewport, n as extractPlanLines, o as MOCKUP_SCREEN_ID_RE, r as formatPlanReview, s as commentViewport, t as formatMockupReview, u as normalizeMockupViewport } from "./mockup-format-D4Ln0WxJ.mjs";
5
- import { t as formatComments } from "./comment-format-D4mCqr7T.mjs";
6
- import { g as createServerAuthMiddleware, h as buildSessionTokenSetCookieValue, v as injectSessionTokenIntoHtml } from "./server-lock-CdaUNZyX.mjs";
7
- import { D as setPrReviewThreadResolved, E as resolvedFromSession, O as submitReview, i as deletePrReviewComment, k as updatePrReviewComment, l as fetchExistingCommentsViaGh, p as fetchPrFileContentViaGh, t as buildPrSession, u as fetchExistingReviewsViaGh, w as refreshPrSession } from "./github-CToLsvPu.mjs";
8
- import { execFileSync } from "node:child_process";
3
+ import { c as writeJsonAtomically, i as loadSettings, s as saveSettings } from "./settings-DIa-3C8o.mjs";
4
+ import { a as formatPlanReview, c as MOCKUP_SCREEN_ID_RE, d as isMockupViewport, f as normalizeMockupViewport, i as extractPlanLines, l as commentViewport, n as lintMockupScreens, o as sectionTitleForLine, p as normalizeSubmitScreens, r as formatMockupReview, s as MOCKUP_MAX_SCREEN_BYTES, u as defaultScreenLabel } from "./mockup-lint-BRjhxXSw.mjs";
5
+ import { a as slugifyDesignId, i as emptyTokens, o as snapshotRevision, r as cloneTokens, s as tokensToCss, t as DEFAULT_DESIGN_SYSTEM_ID } from "./design-system-types-uiP9W2uj.mjs";
6
+ import { t as formatComments } from "./comment-format-DS1wOWj_.mjs";
7
+ import { g as createServerAuthMiddleware, h as buildSessionTokenSetCookieValue, v as injectSessionTokenIntoHtml } from "./server-lock-ACw2cKDI.mjs";
8
+ import { D as setPrReviewThreadResolved, E as resolvedFromSession, O as submitReview, i as deletePrReviewComment, k as updatePrReviewComment, l as fetchExistingCommentsViaGh, p as fetchPrFileContentViaGh, t as buildPrSession, u as fetchExistingReviewsViaGh, w as refreshPrSession } from "./github-BtABlwOZ.mjs";
9
+ import { execFile, execFileSync } from "node:child_process";
9
10
  import { createHash } from "node:crypto";
10
11
  import { fileURLToPath } from "node:url";
11
- import { dirname, extname, join, resolve } from "node:path";
12
+ import { dirname, extname, join, relative, resolve } from "node:path";
12
13
  import { existsSync, mkdirSync, readFileSync, watch } from "node:fs";
13
- import { parseArgs } from "node:util";
14
- import { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
15
- import { homedir } from "node:os";
14
+ import { parseArgs, promisify } from "node:util";
15
+ import { mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
16
+ import { homedir, tmpdir } from "node:os";
16
17
  import { Hono } from "hono";
17
18
  import { streamSSE } from "hono/streaming";
18
19
  import { serve } from "@hono/node-server";
@@ -1446,7 +1447,7 @@ function isReviewCommentSide(value) {
1446
1447
  function planSourceFilePath(storageDir, planId) {
1447
1448
  return join(storageDir, "plan-sources", `${planId}.md`);
1448
1449
  }
1449
- function newId$1() {
1450
+ function newId$2() {
1450
1451
  return crypto.randomUUID();
1451
1452
  }
1452
1453
  /**
@@ -1512,7 +1513,7 @@ function applyUpsert$1(plans, input, now) {
1512
1513
  }
1513
1514
  }
1514
1515
  const plan = {
1515
- id: input.id || newId$1(),
1516
+ id: input.id || newId$2(),
1516
1517
  title: input.title,
1517
1518
  body: input.body,
1518
1519
  source: input.source,
@@ -1744,20 +1745,174 @@ var FilePlanStore = class {
1744
1745
  }
1745
1746
  };
1746
1747
  //#endregion
1748
+ //#region src/lib/mockup-region.ts
1749
+ /**
1750
+ * Replace the inner HTML of the first `[data-diffing="<region>"]` element.
1751
+ * Used by revise_mockup op=replace-region. Not a general HTML rewriter —
1752
+ * good enough for agent-authored mockup fragments.
1753
+ */
1754
+ const VOID_TAGS = new Set([
1755
+ "area",
1756
+ "base",
1757
+ "br",
1758
+ "col",
1759
+ "embed",
1760
+ "hr",
1761
+ "img",
1762
+ "input",
1763
+ "link",
1764
+ "meta",
1765
+ "param",
1766
+ "source",
1767
+ "track",
1768
+ "wbr"
1769
+ ]);
1770
+ function scanOpenTag(html, at) {
1771
+ if (html[at] !== "<" || html[at + 1] === "/" || html[at + 1] === "!") return null;
1772
+ const nameMatch = /^<([A-Za-z][\w:-]*)/.exec(html.slice(at));
1773
+ if (!nameMatch) return null;
1774
+ let quote = null;
1775
+ for (let i = at + 1; i < html.length; i++) {
1776
+ const ch = html[i];
1777
+ if (quote) {
1778
+ if (ch === quote) quote = null;
1779
+ continue;
1780
+ }
1781
+ if (ch === "\"" || ch === "'") {
1782
+ quote = ch;
1783
+ continue;
1784
+ }
1785
+ if (ch === ">") {
1786
+ const selfClosing = html[i - 1] === "/" || VOID_TAGS.has(nameMatch[1].toLowerCase());
1787
+ return {
1788
+ start: at,
1789
+ end: i + 1,
1790
+ name: nameMatch[1],
1791
+ selfClosing
1792
+ };
1793
+ }
1794
+ }
1795
+ return null;
1796
+ }
1797
+ function findOpenTagContaining(html, attrIndex) {
1798
+ let i = attrIndex;
1799
+ while (i >= 0 && html[i] !== "<") i--;
1800
+ if (i < 0) return null;
1801
+ const tag = scanOpenTag(html, i);
1802
+ if (!tag || attrIndex >= tag.end) return null;
1803
+ return tag;
1804
+ }
1805
+ function findMatchingClose(html, afterOpen, tagName) {
1806
+ const lower = tagName.toLowerCase();
1807
+ let depth = 1;
1808
+ let i = afterOpen;
1809
+ while (i < html.length) {
1810
+ if (html[i] !== "<") {
1811
+ i++;
1812
+ continue;
1813
+ }
1814
+ if (html.startsWith("<!--", i)) {
1815
+ const end = html.indexOf("-->", i + 4);
1816
+ i = end === -1 ? html.length : end + 3;
1817
+ continue;
1818
+ }
1819
+ const close = /^<\/([A-Za-z][\w:-]*)\s*>/.exec(html.slice(i));
1820
+ if (close) {
1821
+ if (close[1].toLowerCase() === lower) {
1822
+ depth--;
1823
+ if (depth === 0) return i;
1824
+ }
1825
+ i += close[0].length;
1826
+ continue;
1827
+ }
1828
+ const open = scanOpenTag(html, i);
1829
+ if (open) {
1830
+ if (!open.selfClosing && open.name.toLowerCase() === lower) depth++;
1831
+ i = open.end;
1832
+ continue;
1833
+ }
1834
+ i++;
1835
+ }
1836
+ return null;
1837
+ }
1838
+ const ATTR_RE = /data-diffing\s*=\s*(["'])([^"']*)\1/gi;
1839
+ /** Count how many `data-diffing="<region>"` attributes appear. */
1840
+ function countDataDiffingRegion(html, region) {
1841
+ ATTR_RE.lastIndex = 0;
1842
+ let count = 0;
1843
+ let match;
1844
+ while (match = ATTR_RE.exec(html)) if (match[2] === region) count++;
1845
+ return count;
1846
+ }
1847
+ /**
1848
+ * Replace inner HTML of the first element whose `data-diffing` equals `region`.
1849
+ * `occurrences` is how many matching attributes existed before the replace.
1850
+ */
1851
+ function replaceDataDiffingRegion(html, region, replacement) {
1852
+ if (!region) return {
1853
+ ok: false,
1854
+ error: "region is required"
1855
+ };
1856
+ const occurrences = countDataDiffingRegion(html, region);
1857
+ if (occurrences === 0) return {
1858
+ ok: false,
1859
+ error: `Region "${region}" not found`
1860
+ };
1861
+ ATTR_RE.lastIndex = 0;
1862
+ let match;
1863
+ while (match = ATTR_RE.exec(html)) {
1864
+ if (match[2] !== region) continue;
1865
+ const tag = findOpenTagContaining(html, match.index);
1866
+ if (!tag) return {
1867
+ ok: false,
1868
+ error: `Region "${region}" is not on an element`
1869
+ };
1870
+ if (tag.selfClosing) return {
1871
+ ok: false,
1872
+ error: `Region "${region}" is on a void/self-closing <${tag.name}> — give it a wrapper with inner HTML`
1873
+ };
1874
+ const closeStart = findMatchingClose(html, tag.end, tag.name);
1875
+ if (closeStart === null) return {
1876
+ ok: false,
1877
+ error: `Region "${region}" has no matching </${tag.name}>`
1878
+ };
1879
+ return {
1880
+ ok: true,
1881
+ html: html.slice(0, tag.end) + replacement + html.slice(closeStart),
1882
+ occurrences
1883
+ };
1884
+ }
1885
+ return {
1886
+ ok: false,
1887
+ error: `Region "${region}" not found`
1888
+ };
1889
+ }
1890
+ //#endregion
1747
1891
  //#region src/lib/mockups.ts
1748
1892
  function mockupSourceDir(storageDir, mockupId) {
1749
1893
  return join(storageDir, "mockup-sources", mockupId);
1750
1894
  }
1751
- function newId() {
1895
+ function newId$1() {
1752
1896
  return crypto.randomUUID();
1753
1897
  }
1754
1898
  function snapshotScreens(screens) {
1755
1899
  return screens.map((s) => ({
1756
1900
  id: s.id,
1757
1901
  label: s.label,
1758
- html: s.html
1902
+ html: s.html,
1903
+ stateOf: s.stateOf,
1904
+ flow: s.flow
1759
1905
  }));
1760
1906
  }
1907
+ function snapshotBinding(input) {
1908
+ return {
1909
+ designSystemId: input.designSystemId,
1910
+ designRevision: input.designRevision,
1911
+ mode: input.mode,
1912
+ planId: input.planId,
1913
+ flows: input.flows
1914
+ };
1915
+ }
1761
1916
  function backfillMockup(mockup) {
1762
1917
  const currentVersion = mockup.version ?? 1;
1763
1918
  const have = Array.isArray(mockup.versions) ? mockup.versions.length : 0;
@@ -1793,6 +1948,11 @@ function applyUpsert(mockups, input, now) {
1793
1948
  existing.screens = snapshotScreens(input.screens);
1794
1949
  if (input.source !== void 0) existing.source = input.source;
1795
1950
  if (input.model !== void 0) existing.model = input.model;
1951
+ if (input.designSystemId !== void 0) existing.designSystemId = input.designSystemId;
1952
+ if (input.designRevision !== void 0) existing.designRevision = input.designRevision;
1953
+ if (input.mode !== void 0) existing.mode = input.mode;
1954
+ if (input.planId !== void 0) existing.planId = input.planId;
1955
+ if (input.flows !== void 0) existing.flows = input.flows;
1796
1956
  existing.version += 1;
1797
1957
  existing.updatedAt = now;
1798
1958
  if (!existing.versions) existing.versions = [];
@@ -1802,7 +1962,8 @@ function applyUpsert(mockups, input, now) {
1802
1962
  screens: snapshotScreens(existing.screens),
1803
1963
  source: existing.source,
1804
1964
  model: existing.model,
1805
- createdAt: now
1965
+ createdAt: now,
1966
+ ...snapshotBinding(existing)
1806
1967
  });
1807
1968
  existing.decision = "pending";
1808
1969
  existing.decisionComment = void 0;
@@ -1811,7 +1972,7 @@ function applyUpsert(mockups, input, now) {
1811
1972
  }
1812
1973
  }
1813
1974
  const mockup = {
1814
- id: input.id || newId(),
1975
+ id: input.id || newId$1(),
1815
1976
  title: input.title,
1816
1977
  screens: snapshotScreens(input.screens),
1817
1978
  source: input.source,
@@ -1821,13 +1982,15 @@ function applyUpsert(mockups, input, now) {
1821
1982
  version: 1,
1822
1983
  decision: "pending",
1823
1984
  comments: [],
1985
+ ...snapshotBinding(input),
1824
1986
  versions: [{
1825
1987
  version: 1,
1826
1988
  title: input.title,
1827
1989
  screens: snapshotScreens(input.screens),
1828
1990
  source: input.source,
1829
1991
  model: input.model,
1830
- createdAt: now
1992
+ createdAt: now,
1993
+ ...snapshotBinding(input)
1831
1994
  }]
1832
1995
  };
1833
1996
  mockups.push(mockup);
@@ -1851,6 +2014,7 @@ function syncCurrentVersion(mockup) {
1851
2014
  last.screens = snapshotScreens(mockup.screens);
1852
2015
  last.source = mockup.source;
1853
2016
  last.model = mockup.model;
2017
+ Object.assign(last, snapshotBinding(mockup));
1854
2018
  }
1855
2019
  function versionMismatch(expected, current) {
1856
2020
  if (expected === void 0) return void 0;
@@ -1869,7 +2033,8 @@ function bumpMockupVersion(mockup, now) {
1869
2033
  screens: snapshotScreens(mockup.screens),
1870
2034
  source: mockup.source,
1871
2035
  model: mockup.model,
1872
- createdAt: now
2036
+ createdAt: now,
2037
+ ...snapshotBinding(mockup)
1873
2038
  });
1874
2039
  }
1875
2040
  function applyScreenUpsert(mockup, screen, now) {
@@ -1878,10 +2043,14 @@ function applyScreenUpsert(mockup, screen, now) {
1878
2043
  if (existing) {
1879
2044
  existing.html = html;
1880
2045
  if (screen.label?.trim()) existing.label = screen.label.trim();
2046
+ if (screen.stateOf !== void 0) existing.stateOf = screen.stateOf;
2047
+ if (screen.flow !== void 0) existing.flow = screen.flow;
1881
2048
  } else mockup.screens.push({
1882
2049
  id: screen.id,
1883
2050
  label: screen.label?.trim() || defaultScreenLabel(screen.id),
1884
- html
2051
+ html,
2052
+ stateOf: screen.stateOf,
2053
+ flow: screen.flow
1885
2054
  });
1886
2055
  bumpMockupVersion(mockup, now);
1887
2056
  }
@@ -1892,6 +2061,12 @@ function applyScreenPatch(screen, patch) {
1892
2061
  screen.html = screen.html.slice(0, idx) + patch.replacement + screen.html.slice(idx + patch.expectedText.length);
1893
2062
  return count;
1894
2063
  }
2064
+ function applyScreenRegion(screen, patch) {
2065
+ const result = replaceDataDiffingRegion(screen.html, patch.region, patch.replacement);
2066
+ if (!result.ok) return { error: result.error };
2067
+ screen.html = result.html;
2068
+ return { occurrences: result.occurrences };
2069
+ }
1895
2070
  /** Validate every op against the mockup before applying any (all-or-nothing). */
1896
2071
  function validateThreadBatch(mockup, ops) {
1897
2072
  const comments = mockup.comments ?? [];
@@ -2318,6 +2493,36 @@ var FileMockupStore = class {
2318
2493
  occurrences
2319
2494
  };
2320
2495
  }
2496
+ async replaceRegion(id, screenId, patch, opts) {
2497
+ const mockups = await this.getAll();
2498
+ const mockup = mockups.find((m) => m.id === id);
2499
+ if (!mockup) return {
2500
+ mockup: null,
2501
+ error: "Mockup not found"
2502
+ };
2503
+ const vm = versionMismatch(opts?.expectedVersion, mockup.version);
2504
+ if (vm) return {
2505
+ mockup: null,
2506
+ versionMismatch: vm
2507
+ };
2508
+ const screen = mockup.screens.find((s) => s.id === screenId);
2509
+ if (!screen) return {
2510
+ mockup: null,
2511
+ error: `Screen ${screenId} not found`
2512
+ };
2513
+ const applied = applyScreenRegion(screen, patch);
2514
+ if ("error" in applied) return {
2515
+ mockup: null,
2516
+ error: applied.error
2517
+ };
2518
+ bumpMockupVersion(mockup, Date.now());
2519
+ await this.writeSourceMirror(mockup);
2520
+ await this.save(mockups);
2521
+ return {
2522
+ mockup,
2523
+ occurrences: applied.occurrences
2524
+ };
2525
+ }
2321
2526
  async applyThreadBatch(mockupId, ops) {
2322
2527
  const mockups = await this.getAll();
2323
2528
  const mockup = mockups.find((m) => m.id === mockupId);
@@ -2594,7 +2799,7 @@ function buildMockupProbeScript(opts = {}) {
2594
2799
  function payloadFor(el, ev, kindOverride) {
2595
2800
  // Nearest named [data-diffing] ancestor only — sections never nest deeper.
2596
2801
  const section = el && el.closest ? el.closest('[data-diffing]') : null;
2597
- const kind = kindOverride || (tool === 'pin' ? 'point' : tool === 'section' && section ? 'section' : 'block');
2802
+ const kind = kindOverride || ((tool === 'pin' || tool === 'point') ? 'point' : tool === 'section' && section ? 'section' : 'block');
2598
2803
  const hitEl = deepestBlock(el);
2599
2804
  const outlineEl = kind === 'section' && section ? section : hitEl;
2600
2805
  const pt = ev ? pointPct(ev) : { x: 0, y: 0 };
@@ -2705,7 +2910,7 @@ function buildMockupProbeScript(opts = {}) {
2705
2910
 
2706
2911
  shield.addEventListener('mousemove', (ev) => {
2707
2912
  const target = targetAt(ev);
2708
- if (tool === 'pin') {
2913
+ if (tool === 'pin' || tool === 'point') {
2709
2914
  post('hover', Object.assign(payloadFor(target, ev, 'point'), { kind: 'point' }));
2710
2915
  return;
2711
2916
  }
@@ -2734,7 +2939,7 @@ function buildMockupProbeScript(opts = {}) {
2734
2939
  const dy = ev.clientY - drag.y;
2735
2940
  const dist = Math.hypot(dx, dy);
2736
2941
  drag = null;
2737
- if (tool === 'pin') {
2942
+ if (tool === 'pin' || tool === 'point') {
2738
2943
  post('click', payloadFor(target, ev, 'point'));
2739
2944
  return;
2740
2945
  }
@@ -2754,11 +2959,18 @@ function buildMockupProbeScript(opts = {}) {
2754
2959
  ev.preventDefault();
2755
2960
  ev.stopPropagation();
2756
2961
  }, true);
2757
- post('ready', { sections: sections() });
2962
+ post('ready', { sections: sections(), height: docHeight() });
2963
+ }
2964
+
2965
+ function docHeight() {
2966
+ return Math.max(
2967
+ document.documentElement.scrollHeight,
2968
+ document.body ? document.body.scrollHeight : 0,
2969
+ );
2758
2970
  }
2759
2971
 
2760
2972
  function announce() {
2761
- post('ready', { sections: sections() });
2973
+ post('ready', { sections: sections(), height: docHeight() });
2762
2974
  }
2763
2975
 
2764
2976
  const boot = PASSIVE ? announce : installShield;
@@ -2767,6 +2979,14 @@ function buildMockupProbeScript(opts = {}) {
2767
2979
  } else {
2768
2980
  boot();
2769
2981
  }
2982
+ // Keep the parent's frame height in sync after late-loading media and on
2983
+ // any reflow (the frame is sized to the full document so nothing is cropped).
2984
+ window.addEventListener('load', function () {
2985
+ post('height', { height: docHeight() });
2986
+ });
2987
+ window.addEventListener('resize', function () {
2988
+ post('height', { height: docHeight() });
2989
+ });
2770
2990
  })();`;
2771
2991
  }
2772
2992
  buildMockupProbeScript();
@@ -2786,98 +3006,641 @@ function injectMockupProbe(html, opts = {}) {
2786
3006
  return script + html;
2787
3007
  }
2788
3008
  //#endregion
2789
- //#region src/lib/mockup-lint.ts
2790
- const RULES$1 = [
2791
- {
2792
- label: "tabs",
2793
- re: /role\s*=\s*["']tab(list|panel)?["']/i
2794
- },
2795
- {
2796
- label: "tabs",
2797
- re: /aria-selected\s*=/i
2798
- },
2799
- {
2800
- label: "tabs",
2801
- re: /data-(?:bs-)?toggle\s*=\s*["'](?:tab|pill)["']/i
2802
- },
2803
- {
2804
- label: "tabs",
2805
- re: /\bdata-tabs?\s*=/i
2806
- },
2807
- {
2808
- label: "accordion",
2809
- re: /<details[\s>]/i
2810
- },
2811
- {
2812
- label: "accordion",
2813
- re: /data-(?:bs-)?toggle\s*=\s*["']collapse["']/i
2814
- },
2815
- {
2816
- label: "accordion",
2817
- re: /class\s*=\s*["'][^"']*\baccordion\b/i
2818
- },
2819
- {
2820
- label: "modal",
2821
- re: /role\s*=\s*["'](?:alert)?dialog["']/i
2822
- },
2823
- {
2824
- label: "modal",
2825
- re: /data-(?:bs-)?toggle\s*=\s*["']modal["']/i
2826
- },
2827
- {
2828
- label: "modal",
2829
- re: /class\s*=\s*["'][^"']*\bmodal\b/i
2830
- },
2831
- {
2832
- label: "dropdown",
2833
- re: /role\s*=\s*["']menu(?:bar)?["']/i
2834
- },
2835
- {
2836
- label: "dropdown",
2837
- re: /data-(?:bs-)?toggle\s*=\s*["']dropdown["']/i
2838
- },
2839
- {
2840
- label: "dropdown",
2841
- re: /class\s*=\s*["'][^"']*\bdropdown\b/i
2842
- },
2843
- {
2844
- label: "toggle",
2845
- re: /role\s*=\s*["']switch["']/i
2846
- },
2847
- {
2848
- label: "toggle",
2849
- re: /class\s*=\s*["'][^"']*\b(?:toggle|switch)\b/i
3009
+ //#region src/lib/design-system.ts
3010
+ function newId() {
3011
+ return crypto.randomUUID();
3012
+ }
3013
+ function backfill(system) {
3014
+ if (!system.tokens) system.tokens = emptyTokens();
3015
+ if (!system.tokens.raw) system.tokens.raw = {};
3016
+ if (!system.tokens.color) system.tokens.color = {};
3017
+ if (!system.tokens.font) system.tokens.font = {};
3018
+ if (!system.tokens.radius) system.tokens.radius = {};
3019
+ if (!system.tokens.shadow) system.tokens.shadow = {};
3020
+ if (!system.tokens.space) system.tokens.space = {
3021
+ unit: 4,
3022
+ scale: [
3023
+ 4,
3024
+ 8,
3025
+ 12,
3026
+ 16,
3027
+ 24,
3028
+ 32,
3029
+ 48
3030
+ ]
3031
+ };
3032
+ if (!system.components) system.components = [];
3033
+ if (!system.sources) system.sources = [];
3034
+ if (!system.comments) system.comments = [];
3035
+ if (!system.revisions) system.revisions = [];
3036
+ if (!system.guidelines) system.guidelines = "";
3037
+ if (!system.status) system.status = system.revision > 0 ? "published" : "draft";
3038
+ }
3039
+ function applyFields(system, input, now) {
3040
+ if (input.title?.trim()) system.title = input.title.trim();
3041
+ if (input.tokens) system.tokens = cloneTokens(input.tokens);
3042
+ if (input.guidelines !== void 0) system.guidelines = input.guidelines;
3043
+ if (input.components) system.components = input.components.map((c) => ({ ...c }));
3044
+ if (input.sources) system.sources = input.sources.map((s) => ({ ...s }));
3045
+ if (input.status) system.status = input.status;
3046
+ system.updatedAt = now;
3047
+ }
3048
+ var InMemoryDesignSystemStore = class {
3049
+ systems = [];
3050
+ async getAll() {
3051
+ for (const s of this.systems) backfill(s);
3052
+ return this.systems;
2850
3053
  }
3054
+ async get(id) {
3055
+ const system = this.systems.find((s) => s.id === id) ?? null;
3056
+ if (system) backfill(system);
3057
+ return system;
3058
+ }
3059
+ async getDefault() {
3060
+ const published = this.systems.find((s) => s.id === "default" && s.status === "published");
3061
+ if (published) {
3062
+ backfill(published);
3063
+ return published;
3064
+ }
3065
+ const any = this.systems.find((s) => s.status === "published") ?? this.systems[0];
3066
+ if (any) backfill(any);
3067
+ return any ?? null;
3068
+ }
3069
+ async upsert(input) {
3070
+ const now = Date.now();
3071
+ const id = slugifyDesignId(input.id ?? "default") ?? "default";
3072
+ const existing = this.systems.find((s) => s.id === id);
3073
+ if (existing) {
3074
+ applyFields(existing, input, now);
3075
+ return existing;
3076
+ }
3077
+ const system = {
3078
+ id,
3079
+ title: input.title?.trim() || "Default",
3080
+ revision: 0,
3081
+ status: input.status ?? "draft",
3082
+ tokens: input.tokens ? cloneTokens(input.tokens) : emptyTokens(),
3083
+ guidelines: input.guidelines ?? "",
3084
+ components: (input.components ?? []).map((c) => ({ ...c })),
3085
+ sources: (input.sources ?? []).map((s) => ({ ...s })),
3086
+ createdAt: now,
3087
+ updatedAt: now,
3088
+ revisions: [],
3089
+ comments: []
3090
+ };
3091
+ this.systems.push(system);
3092
+ return system;
3093
+ }
3094
+ async propose(id, input) {
3095
+ const system = await this.get(id);
3096
+ if (!system) return null;
3097
+ applyFields(system, input, Date.now());
3098
+ system.status = "draft";
3099
+ return system;
3100
+ }
3101
+ async publish(id) {
3102
+ const system = await this.get(id);
3103
+ if (!system) return null;
3104
+ const now = Date.now();
3105
+ system.revision += 1;
3106
+ system.status = "published";
3107
+ system.updatedAt = now;
3108
+ system.publishedAt = now;
3109
+ system.revisions.push(snapshotRevision(system));
3110
+ return system;
3111
+ }
3112
+ async remove(id) {
3113
+ const idx = this.systems.findIndex((s) => s.id === id);
3114
+ if (idx === -1) return false;
3115
+ this.systems.splice(idx, 1);
3116
+ return true;
3117
+ }
3118
+ async addComponent(id, component) {
3119
+ const system = await this.get(id);
3120
+ if (!system) return null;
3121
+ const slug = slugifyDesignId(component.id);
3122
+ if (!slug) return null;
3123
+ const next = {
3124
+ ...component,
3125
+ id: slug,
3126
+ createdAt: component.createdAt ?? Date.now()
3127
+ };
3128
+ const idx = system.components.findIndex((c) => c.id === slug);
3129
+ if (idx >= 0) system.components[idx] = next;
3130
+ else system.components.push(next);
3131
+ system.updatedAt = Date.now();
3132
+ system.status = "draft";
3133
+ return system;
3134
+ }
3135
+ async removeComponent(id, componentId) {
3136
+ const system = await this.get(id);
3137
+ if (!system) return null;
3138
+ const before = system.components.length;
3139
+ system.components = system.components.filter((c) => c.id !== componentId);
3140
+ if (system.components.length === before) return system;
3141
+ system.updatedAt = Date.now();
3142
+ system.status = "draft";
3143
+ return system;
3144
+ }
3145
+ async addComment(id, comment) {
3146
+ const system = await this.get(id);
3147
+ if (!system) return null;
3148
+ system.comments.push({
3149
+ id: newId(),
3150
+ kind: comment.kind,
3151
+ target: comment.target,
3152
+ body: comment.body,
3153
+ status: comment.status ?? "open",
3154
+ createdAt: Date.now(),
3155
+ replies: []
3156
+ });
3157
+ system.updatedAt = Date.now();
3158
+ return system;
3159
+ }
3160
+ async updateComment(id, commentId, fields) {
3161
+ const system = await this.get(id);
3162
+ if (!system) return null;
3163
+ const comment = system.comments.find((c) => c.id === commentId);
3164
+ if (!comment) return null;
3165
+ if (fields.body !== void 0) comment.body = fields.body;
3166
+ if (fields.status) comment.status = fields.status;
3167
+ system.updatedAt = Date.now();
3168
+ return system;
3169
+ }
3170
+ async addReply(id, commentId, reply) {
3171
+ const system = await this.get(id);
3172
+ if (!system) return null;
3173
+ const comment = system.comments.find((c) => c.id === commentId);
3174
+ if (!comment) return null;
3175
+ comment.replies.push(reply);
3176
+ system.updatedAt = Date.now();
3177
+ return system;
3178
+ }
3179
+ };
3180
+ var FileDesignSystemStore = class {
3181
+ dirPath;
3182
+ filePath;
3183
+ memory = new InMemoryDesignSystemStore();
3184
+ loaded = false;
3185
+ constructor(storageDir) {
3186
+ this.dirPath = storageDir ?? getProjectStorageDir();
3187
+ this.filePath = join(this.dirPath, "design-system.json");
3188
+ }
3189
+ async load() {
3190
+ if (this.loaded) return;
3191
+ this.loaded = true;
3192
+ try {
3193
+ const raw = await readFile(this.filePath, "utf8");
3194
+ const parsed = JSON.parse(raw);
3195
+ const systems = Array.isArray(parsed.systems) ? parsed.systems : [];
3196
+ this.memory.systems = systems;
3197
+ for (const system of systems) backfill(system);
3198
+ } catch {}
3199
+ }
3200
+ async persist() {
3201
+ await mkdir(this.dirPath, { recursive: true });
3202
+ const systems = await this.memory.getAll();
3203
+ writeJsonAtomically(this.filePath, { systems });
3204
+ try {
3205
+ const tokens = systems.find((s) => s.status === "published") ?? systems[0];
3206
+ if (tokens) {
3207
+ const { tokensToCss } = await import("./design-system-types-QR9xS7o3.mjs");
3208
+ const dir = join(this.dirPath, "design-system");
3209
+ await mkdir(dir, { recursive: true });
3210
+ await writeFile(join(dir, `${tokens.id}.css`), tokensToCss(tokens.tokens), "utf8");
3211
+ }
3212
+ } catch {}
3213
+ }
3214
+ async getAll() {
3215
+ await this.load();
3216
+ return this.memory.getAll();
3217
+ }
3218
+ async get(id) {
3219
+ await this.load();
3220
+ return this.memory.get(id);
3221
+ }
3222
+ async getDefault() {
3223
+ await this.load();
3224
+ return this.memory.getDefault();
3225
+ }
3226
+ async upsert(input) {
3227
+ await this.load();
3228
+ const system = await this.memory.upsert(input);
3229
+ await this.persist();
3230
+ return system;
3231
+ }
3232
+ async propose(id, input) {
3233
+ await this.load();
3234
+ const system = await this.memory.propose(id, input);
3235
+ if (system) await this.persist();
3236
+ return system;
3237
+ }
3238
+ async publish(id) {
3239
+ await this.load();
3240
+ const system = await this.memory.publish(id);
3241
+ if (system) await this.persist();
3242
+ return system;
3243
+ }
3244
+ async remove(id) {
3245
+ await this.load();
3246
+ const ok = await this.memory.remove(id);
3247
+ if (ok) await this.persist();
3248
+ return ok;
3249
+ }
3250
+ async addComponent(id, component) {
3251
+ await this.load();
3252
+ const system = await this.memory.addComponent(id, component);
3253
+ if (system) await this.persist();
3254
+ return system;
3255
+ }
3256
+ async removeComponent(id, componentId) {
3257
+ await this.load();
3258
+ const system = await this.memory.removeComponent(id, componentId);
3259
+ if (system) await this.persist();
3260
+ return system;
3261
+ }
3262
+ async addComment(id, comment) {
3263
+ await this.load();
3264
+ const system = await this.memory.addComment(id, comment);
3265
+ if (system) await this.persist();
3266
+ return system;
3267
+ }
3268
+ async updateComment(id, commentId, fields) {
3269
+ await this.load();
3270
+ const system = await this.memory.updateComment(id, commentId, fields);
3271
+ if (system) await this.persist();
3272
+ return system;
3273
+ }
3274
+ async addReply(id, commentId, reply) {
3275
+ await this.load();
3276
+ const system = await this.memory.addReply(id, commentId, reply);
3277
+ if (system) await this.persist();
3278
+ return system;
3279
+ }
3280
+ };
3281
+ //#endregion
3282
+ //#region src/lib/design-system-extract.ts
3283
+ const SKIP_DIRS = new Set([
3284
+ "node_modules",
3285
+ ".git",
3286
+ "dist",
3287
+ "build",
3288
+ ".next",
3289
+ "coverage",
3290
+ "target",
3291
+ ".turbo",
3292
+ ".output"
3293
+ ]);
3294
+ const CSS_FILE_RE = /\.(css|scss|sass)$/i;
3295
+ const TOKEN_JSON_RE = /(tokens?|theme)\.json$/i;
3296
+ const VAR_RE = /--([a-zA-Z0-9_-]+)\s*:\s*([^;]+);/g;
3297
+ const COLOR_ALIASES = [
3298
+ [/(?:^|-)(?:bg|background|canvas|surface-0)$/i, "bg"],
3299
+ [/(?:^|-)(?:surface|raised|panel)$/i, "surface"],
3300
+ [/(?:^|-)(?:text|fg|foreground|ink)$/i, "text"],
3301
+ [/(?:^|-)(?:muted|subtle|secondary-text)$/i, "muted"],
3302
+ [/(?:^|-)(?:accent|primary|brand|focus)$/i, "accent"],
3303
+ [/(?:^|-)(?:border|rule|line)$/i, "border"],
3304
+ [/(?:^|-)(?:danger|error|destructive)$/i, "danger"],
3305
+ [/(?:^|-)(?:ok|success)$/i, "ok"],
3306
+ [/(?:^|-)(?:warn|warning)$/i, "warn"]
2851
3307
  ];
2852
- const ORDERED_LABELS = [
2853
- "tabs",
2854
- "accordion",
2855
- "modal",
2856
- "dropdown",
2857
- "toggle"
2858
- ];
2859
- /** Find the in-page state UI patterns present in one screen's HTML. */
2860
- function detectInPageState(html) {
2861
- const found = /* @__PURE__ */ new Set();
2862
- for (const rule of RULES$1) {
2863
- if (found.has(rule.label)) continue;
2864
- if (rule.re.test(html)) found.add(rule.label);
2865
- }
2866
- return ORDERED_LABELS.filter((label) => found.has(label));
2867
- }
2868
- /** Build submit-time hints for a set of screens (empty when all clean). */
2869
- function lintMockupScreens(screens) {
2870
- const hints = [];
2871
- for (const screen of screens) {
2872
- const patterns = detectInPageState(screen.html);
2873
- if (patterns.length === 0) continue;
2874
- hints.push({
2875
- screenId: screen.id,
2876
- patterns,
2877
- message: `Screen "${screen.id}" contains in-page state UI (${patterns.join(", ")}). Split each state into its own screen instead.`
3308
+ const FONT_ALIASES = [[/(?:ui|sans|body|text)-?font|font-?(?:ui|sans|body)/i, "ui"], [/(?:mono|code|pre)-?font|font-?(?:mono|code)/i, "mono"]];
3309
+ function mapSemantic(name, value, tokens) {
3310
+ const trimmed = value.trim();
3311
+ for (const [re, key] of COLOR_ALIASES) if (re.test(name) && !tokens.color[key]) {
3312
+ tokens.color[key] = trimmed;
3313
+ return;
3314
+ }
3315
+ for (const [re, key] of FONT_ALIASES) if (re.test(name) && !tokens.font[key]) {
3316
+ tokens.font[key] = trimmed.replace(/^["']|["']$/g, "");
3317
+ return;
3318
+ }
3319
+ if (/radius/i.test(name) && !tokens.radius.sm) tokens.radius.sm = trimmed;
3320
+ if (/shadow|elevation/i.test(name) && !tokens.shadow.overlay) tokens.shadow.overlay = trimmed;
3321
+ }
3322
+ /** Pure: pull custom properties out of a CSS/JSON string. */
3323
+ function extractTokensFromText(text) {
3324
+ const tokens = emptyTokens();
3325
+ VAR_RE.lastIndex = 0;
3326
+ let match;
3327
+ while (match = VAR_RE.exec(text)) {
3328
+ const name = match[1];
3329
+ const value = match[2].trim();
3330
+ tokens.raw[`--${name}`] = value;
3331
+ mapSemantic(name, value, tokens);
3332
+ }
3333
+ try {
3334
+ const json = JSON.parse(text);
3335
+ if (json && typeof json === "object") collectJsonTokens(json, tokens, "");
3336
+ } catch {}
3337
+ return tokens;
3338
+ }
3339
+ function collectJsonTokens(value, tokens, path) {
3340
+ if (!value || typeof value !== "object") return;
3341
+ if (Array.isArray(value)) return;
3342
+ for (const [k, v] of Object.entries(value)) {
3343
+ const next = path ? `${path}-${k}` : k;
3344
+ if (typeof v === "string") {
3345
+ tokens.raw[next] = v;
3346
+ mapSemantic(next, v, tokens);
3347
+ } else if (v && typeof v === "object" && "value" in v) {
3348
+ const inner = v.value;
3349
+ if (typeof inner === "string") {
3350
+ tokens.raw[next] = inner;
3351
+ mapSemantic(next, inner, tokens);
3352
+ }
3353
+ } else collectJsonTokens(v, tokens, next);
3354
+ }
3355
+ }
3356
+ function mergeTokens(base, extra) {
3357
+ return {
3358
+ color: {
3359
+ ...base.color,
3360
+ ...extra.color
3361
+ },
3362
+ font: {
3363
+ ...base.font,
3364
+ ...extra.font
3365
+ },
3366
+ space: extra.space.scale.length ? extra.space : base.space,
3367
+ radius: {
3368
+ ...base.radius,
3369
+ ...extra.radius
3370
+ },
3371
+ shadow: {
3372
+ ...base.shadow,
3373
+ ...extra.shadow
3374
+ },
3375
+ raw: {
3376
+ ...base.raw,
3377
+ ...extra.raw
3378
+ }
3379
+ };
3380
+ }
3381
+ async function walkFiles(root, dir, out, budget) {
3382
+ if (budget.left <= 0) return;
3383
+ let entries;
3384
+ try {
3385
+ entries = await readdir(dir, { withFileTypes: true });
3386
+ } catch {
3387
+ return;
3388
+ }
3389
+ for (const entry of entries) {
3390
+ if (budget.left <= 0) return;
3391
+ if (entry.name.startsWith(".") && entry.name !== ".") continue;
3392
+ const full = join(dir, entry.name);
3393
+ if (entry.isDirectory()) {
3394
+ if (SKIP_DIRS.has(entry.name)) continue;
3395
+ await walkFiles(root, full, out, budget);
3396
+ continue;
3397
+ }
3398
+ if (!entry.isFile()) continue;
3399
+ if (CSS_FILE_RE.test(entry.name) || TOKEN_JSON_RE.test(entry.name)) {
3400
+ out.push(full);
3401
+ budget.left -= 1;
3402
+ }
3403
+ }
3404
+ }
3405
+ /** Scan a repo for CSS variables / token JSON. Bounded and skippable. */
3406
+ async function extractFromRepo(repoRoot, opts) {
3407
+ const files = [];
3408
+ await walkFiles(repoRoot, repoRoot, files, { left: opts?.maxFiles ?? 80 });
3409
+ let tokens = emptyTokens();
3410
+ const hits = [];
3411
+ const sources = [];
3412
+ for (const file of files) {
3413
+ let text = "";
3414
+ try {
3415
+ text = await readFile(file, "utf8");
3416
+ } catch {
3417
+ continue;
3418
+ }
3419
+ const extracted = extractTokensFromText(text);
3420
+ const count = Object.keys(extracted.raw).length;
3421
+ if (count === 0) continue;
3422
+ tokens = mergeTokens(tokens, extracted);
3423
+ const rel = relative(repoRoot, file) || file;
3424
+ hits.push({
3425
+ path: rel,
3426
+ count
3427
+ });
3428
+ sources.push({
3429
+ kind: "css-vars",
3430
+ path: rel
2878
3431
  });
2879
3432
  }
2880
- return hints;
3433
+ return {
3434
+ tokens,
3435
+ sources,
3436
+ files: hits
3437
+ };
3438
+ }
3439
+ //#endregion
3440
+ //#region src/lib/mockup-shell.ts
3441
+ const MOCKUP_SLOT = "data-diffing-slot";
3442
+ const RESET_CSS = `*,*::before,*::after{box-sizing:border-box}
3443
+ html,body{margin:0;padding:0;background:var(--ds-color-bg,#fff);color:var(--ds-color-text,#111);font-family:var(--ds-font-ui,ui-sans-serif,system-ui,sans-serif);line-height:1.45}
3444
+ img,svg,video{max-width:100%;display:block}
3445
+ button,input,select,textarea{font:inherit}
3446
+ `;
3447
+ function appShell(components, inner) {
3448
+ const shell = components?.find((c) => c.id === "app-shell");
3449
+ if (!shell?.html) return `<main ${MOCKUP_SLOT}="content" data-diffing="content">${inner}</main>`;
3450
+ if (shell.html.includes("{{content}}") || shell.html.includes("data-diffing-slot=\"content\"")) return shell.html.replace("{{content}}", inner).replace(new RegExp(`(<[^>]+${MOCKUP_SLOT}=["']content["'][^>]*>)([\\s\\S]*?)(</[^>]+>)`), `$1${inner}$3`);
3451
+ return `${shell.html}<main ${MOCKUP_SLOT}="content" data-diffing="content">${inner}</main>`;
3452
+ }
3453
+ /** Wrap a fragment in the host document. Stored HTML stays unwrapped. */
3454
+ function wrapMockupFragment(fragment, opts) {
3455
+ const theme = opts.theme ?? "light";
3456
+ const css = `${RESET_CSS}\n${tokensToCss(opts.tokens)}`;
3457
+ const body = appShell(opts.components, fragment);
3458
+ return `<!DOCTYPE html>
3459
+ <html lang="en" data-theme="${theme}">
3460
+ <head>
3461
+ <meta charset="utf-8"/>
3462
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
3463
+ <title>${escapeHtml(opts.title ?? "Mockup")}</title>
3464
+ <style>${css}</style>
3465
+ </head>
3466
+ <body>
3467
+ ${body}
3468
+ </body>
3469
+ </html>
3470
+ `;
3471
+ }
3472
+ function resolveRenderMode(requested, system) {
3473
+ if (requested === "document" || requested === "fragment") return requested;
3474
+ return system && system.status === "published" ? "fragment" : "document";
3475
+ }
3476
+ function renderMockupHtml(html, opts) {
3477
+ if (opts.mode !== "fragment" || !opts.system) return html;
3478
+ if (/<!doctype\s+html|<html[\s>]/i.test(html)) return html;
3479
+ return wrapMockupFragment(html, {
3480
+ tokens: opts.system.tokens,
3481
+ components: opts.system.components,
3482
+ title: opts.title,
3483
+ theme: opts.theme
3484
+ });
3485
+ }
3486
+ function escapeHtml(value) {
3487
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3488
+ }
3489
+ //#endregion
3490
+ //#region src/lib/mockup-preview.ts
3491
+ const execFileAsync = promisify(execFile);
3492
+ const VIEWPORT_WIDTH = {
3493
+ desktop: 1280,
3494
+ tablet: 768,
3495
+ mobile: 390
3496
+ };
3497
+ function analyzeMockupHtml(html) {
3498
+ const missingImages = [];
3499
+ const imgRe = /<img\b[^>]*>/gi;
3500
+ let match;
3501
+ while (match = imgRe.exec(html)) {
3502
+ const tag = match[0];
3503
+ const src = /\bsrc\s*=\s*(["'])([^"']*)\1/i.exec(tag)?.[2] ?? "";
3504
+ if (!src || src === "#" || src.startsWith("about:")) missingImages.push(src || "(empty)");
3505
+ }
3506
+ const externalStylesheets = [...html.matchAll(/<link\b[^>]*rel=["']stylesheet["'][^>]*>/gi)].map((m) => /\bhref\s*=\s*(["'])([^"']*)\1/i.exec(m[0])?.[2] ?? "").filter((href) => /^https?:/i.test(href));
3507
+ const notes = [];
3508
+ if (/overflow:\s*visible/i.test(html) && /white-space:\s*nowrap/i.test(html)) notes.push("Possible overflow: nowrap + visible overflow");
3509
+ if (html.length > 2e5) notes.push("Screen HTML is large; prefer fragments");
3510
+ return {
3511
+ missingImages,
3512
+ externalStylesheets,
3513
+ notes
3514
+ };
3515
+ }
3516
+ async function findChrome() {
3517
+ const names = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium"] : process.platform === "win32" ? [] : [
3518
+ "google-chrome",
3519
+ "chromium",
3520
+ "chromium-browser",
3521
+ "google-chrome-stable"
3522
+ ];
3523
+ for (const name of names) try {
3524
+ if (name.startsWith("/")) {
3525
+ await execFileAsync(name, ["--version"], { timeout: 4e3 });
3526
+ return name;
3527
+ }
3528
+ const { stdout } = await execFileAsync("which", [name], { timeout: 4e3 });
3529
+ const path = stdout.trim();
3530
+ if (path) return path;
3531
+ } catch {}
3532
+ return null;
3533
+ }
3534
+ async function renderMockupPreview(html, opts = {}) {
3535
+ const viewport = opts.viewport ?? "desktop";
3536
+ const width = VIEWPORT_WIDTH[viewport];
3537
+ const report = analyzeMockupHtml(html);
3538
+ let chrome = null;
3539
+ try {
3540
+ chrome = await findChrome();
3541
+ } catch {
3542
+ chrome = null;
3543
+ }
3544
+ if (!chrome) return {
3545
+ available: false,
3546
+ reason: "No Chrome/Chromium on PATH — layout report only",
3547
+ viewport,
3548
+ width,
3549
+ report
3550
+ };
3551
+ const dir = await mkdtemp(join(tmpdir(), "diffing-preview-"));
3552
+ const htmlPath = join(dir, "screen.html");
3553
+ const pngPath = join(dir, "screen.png");
3554
+ try {
3555
+ await writeFile(htmlPath, html, "utf8");
3556
+ await execFileAsync(chrome, [
3557
+ "--headless=new",
3558
+ "--disable-gpu",
3559
+ "--hide-scrollbars",
3560
+ `--window-size=${width},900`,
3561
+ `--screenshot=${pngPath}`,
3562
+ `file://${htmlPath}`
3563
+ ], { timeout: 2e4 });
3564
+ return {
3565
+ available: true,
3566
+ screenshotBase64: (await readFile(pngPath)).toString("base64"),
3567
+ mime: "image/png",
3568
+ viewport,
3569
+ width,
3570
+ report
3571
+ };
3572
+ } catch (err) {
3573
+ return {
3574
+ available: false,
3575
+ reason: err instanceof Error ? err.message : "Screenshot failed",
3576
+ viewport,
3577
+ width,
3578
+ report
3579
+ };
3580
+ } finally {
3581
+ await rm(dir, {
3582
+ recursive: true,
3583
+ force: true
3584
+ }).catch(() => void 0);
3585
+ }
3586
+ }
3587
+ //#endregion
3588
+ //#region src/lib/mockup-handoff.ts
3589
+ function buildMockupHandoff(mockup, system) {
3590
+ const used = /* @__PURE__ */ new Set();
3591
+ const blob = mockup.screens.map((s) => s.html).join("\n");
3592
+ for (const component of system?.components ?? []) if (blob.includes(`data-diffing="${component.id}"`) || blob.includes(component.id)) used.add(component.id);
3593
+ const openNits = (mockup.comments ?? []).filter((c) => c.status === "open").map((c) => ({
3594
+ id: c.id,
3595
+ screenId: c.screenId,
3596
+ body: c.body
3597
+ }));
3598
+ return {
3599
+ mockupId: mockup.id,
3600
+ title: mockup.title,
3601
+ version: mockup.version,
3602
+ decision: mockup.decision,
3603
+ designSystemId: mockup.designSystemId ?? system?.id,
3604
+ designRevision: mockup.designRevision ?? system?.revision,
3605
+ mode: mockup.mode,
3606
+ planId: mockup.planId,
3607
+ tokens: system?.tokens,
3608
+ tokenCss: system ? tokensToCss(system.tokens) : void 0,
3609
+ componentsUsed: [...used],
3610
+ screens: mockup.screens.map((s) => ({
3611
+ id: s.id,
3612
+ label: s.label,
3613
+ stateOf: s.stateOf,
3614
+ flow: s.flow,
3615
+ intent: s.stateOf ? `${s.label} (state of ${s.stateOf})` : s.label
3616
+ })),
3617
+ openNits,
3618
+ guidance: "This is a mockup. Match feel (type, color, density, chrome) — not class names or a pixel grid."
3619
+ };
3620
+ }
3621
+ function formatMockupHandoffXml(handoff) {
3622
+ const screens = handoff.screens.map((s) => ` <screen id="${escape(s.id)}" label="${escape(s.label)}"${s.stateOf ? ` state-of="${escape(s.stateOf)}"` : ""}${s.flow ? ` flow="${escape(s.flow)}"` : ""}>${escape(s.intent)}</screen>`).join("\n");
3623
+ const nits = handoff.openNits.length === 0 ? "" : ` <open-nits>\n${handoff.openNits.map((n) => ` <nit id="${escape(n.id)}" screen="${escape(n.screenId)}">${escape(n.body)}</nit>`).join("\n")}\n </open-nits>\n`;
3624
+ return `<mockup-handoff>
3625
+ <mockup id="${escape(handoff.mockupId)}" title="${escape(handoff.title)}" version="${handoff.version}" decision="${escape(handoff.decision)}"${handoff.designSystemId ? ` design-system="${escape(handoff.designSystemId)}"` : ""}${handoff.designRevision ? ` design-revision="${handoff.designRevision}"` : ""}${handoff.mode ? ` mode="${escape(handoff.mode)}"` : ""}${handoff.planId ? ` plan="${escape(handoff.planId)}"` : ""}>
3626
+ <guidance>${escape(handoff.guidance)}</guidance>
3627
+ <screens>
3628
+ ${screens}
3629
+ </screens>
3630
+ ${nits} </mockup>
3631
+ </mockup-handoff>`;
3632
+ }
3633
+ function escape(value) {
3634
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3635
+ }
3636
+ //#endregion
3637
+ //#region src/lib/mockup-suggestion.ts
3638
+ const SUGGESTION_RE = /```suggestion\n([\s\S]*?)```/;
3639
+ function extractSuggestion(body) {
3640
+ const match = SUGGESTION_RE.exec(body);
3641
+ if (!match) return null;
3642
+ const text = match[1].replace(/\n$/, "");
3643
+ return text.length > 0 ? text : null;
2881
3644
  }
2882
3645
  //#endregion
2883
3646
  //#region src/lib/state.ts
@@ -4378,7 +5141,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
4378
5141
  bindHost: "127.0.0.1",
4379
5142
  authToken: null,
4380
5143
  insecureNoAuth: true
4381
- }, mockupStore) {
5144
+ }, mockupStore, designSystemStore) {
4382
5145
  const app = new Hono();
4383
5146
  app.use("*", createServerAuthMiddleware(security));
4384
5147
  let diffOpts = { ...diffOptsInput };
@@ -4386,6 +5149,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
4386
5149
  const store = commentStore ?? new FileCommentStore();
4387
5150
  const plans = planStore ?? new FilePlanStore();
4388
5151
  const mockups = mockupStore ?? new FileMockupStore();
5152
+ const designSystems = designSystemStore ?? new FileDesignSystemStore();
4389
5153
  const prStore = prSessionStore ?? new FilePrSessionStore();
4390
5154
  const agentDiffCache = new AgentDiffIndexCache();
4391
5155
  const uiStateStore = new FileUiStateStore();
@@ -4440,6 +5204,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
4440
5204
  let commentsDebounce = null;
4441
5205
  let plansDebounce = null;
4442
5206
  let mockupsDebounce = null;
5207
+ let designSystemDebounce = null;
4443
5208
  let prSessionDebounce = null;
4444
5209
  watch(storageDir, (_eventType, filename) => {
4445
5210
  if (!filename) return;
@@ -4452,6 +5217,9 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
4452
5217
  } else if (filename.startsWith("mockups.json")) {
4453
5218
  if (mockupsDebounce) clearTimeout(mockupsDebounce);
4454
5219
  mockupsDebounce = setTimeout(() => broadcast("mockups", Date.now().toString()), 120);
5220
+ } else if (filename.startsWith("design-system.json")) {
5221
+ if (designSystemDebounce) clearTimeout(designSystemDebounce);
5222
+ designSystemDebounce = setTimeout(() => broadcast("design-system", Date.now().toString()), 120);
4455
5223
  } else if (filename.startsWith("pr-session.json")) {
4456
5224
  if (prSessionDebounce) clearTimeout(prSessionDebounce);
4457
5225
  prSessionDebounce = setTimeout(() => broadcast("pr-session", Date.now().toString()), 120);
@@ -5119,7 +5887,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5119
5887
  if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
5120
5888
  const absolutePath = resolve(root, relPath);
5121
5889
  const content = await readFile(absolutePath, "utf-8");
5122
- const { applySuggestionToContent } = await import("./apply-suggestion-BsR_8FXW.mjs");
5890
+ const { applySuggestionToContent } = await import("./apply-suggestion-tLsWREyb.mjs");
5123
5891
  const result = applySuggestionToContent({
5124
5892
  content,
5125
5893
  lineNumber: comment.lineNumber,
@@ -5366,7 +6134,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5366
6134
  const session = await prStore.get();
5367
6135
  if (!session) return notInPrMode(c);
5368
6136
  try {
5369
- const { fetchPrChecks } = await import("./github--rpPZ3vK.mjs");
6137
+ const { fetchPrChecks } = await import("./github-BuBcy-co.mjs");
5370
6138
  const checks = await fetchPrChecks(resolvedFromSession(session), session.headSha);
5371
6139
  const summary = {
5372
6140
  total: checks.length,
@@ -5398,7 +6166,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5398
6166
  const text = typeof body.body === "string" ? body.body.trim() : "";
5399
6167
  if (!text) return c.json({ error: "body is required" }, 400);
5400
6168
  try {
5401
- const { replyToPrComment } = await import("./github--rpPZ3vK.mjs");
6169
+ const { replyToPrComment } = await import("./github-BuBcy-co.mjs");
5402
6170
  const result = await replyToPrComment({
5403
6171
  resolved: resolvedFromSession(session),
5404
6172
  inReplyTo: id,
@@ -5611,8 +6379,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5611
6379
  if (decision !== "approve" && decision !== "comment" && decision !== "request-changes" && decision !== "draft") return c.json({ error: "decision must be one of: approve, comment, request-changes, draft" }, 400);
5612
6380
  const generalBody = typeof body.body === "string" ? body.body : "";
5613
6381
  if (body.dryRun === true) {
5614
- const { buildReviewPayload, resolvedFromSession } = await import("./github--rpPZ3vK.mjs");
5615
- const { rewriteLocalAttachmentsInBodies } = await import("./github-attachments-DEvAeo5P.mjs");
6382
+ const { buildReviewPayload, resolvedFromSession } = await import("./github-BuBcy-co.mjs");
6383
+ const { rewriteLocalAttachmentsInBodies } = await import("./github-attachments-Bwr25ItV.mjs");
5616
6384
  const resolved = resolvedFromSession(session);
5617
6385
  const comments = session.comments ?? [];
5618
6386
  const rewritten = await rewriteLocalAttachmentsInBodies(resolved, [generalBody, ...comments.map((c) => c.body ?? "")], { dryRun: true });
@@ -5709,7 +6477,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5709
6477
  const openCount = all.filter((x) => x.status === "open").length;
5710
6478
  let diffFingerprints;
5711
6479
  try {
5712
- const { fingerprintDiffFiles } = await import("./diff-fingerprint-BL5jNHnP.mjs");
6480
+ const { fingerprintDiffFiles } = await import("./diff-fingerprint-PnC8Fs1-.mjs");
5713
6481
  if (prMode) {
5714
6482
  const prSession = await prStore.get();
5715
6483
  if (prSession?.diff) diffFingerprints = fingerprintDiffFiles(prSession.diff);
@@ -5777,7 +6545,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5777
6545
  } catch (err) {
5778
6546
  return c.json({ error: err?.message ?? "Failed to compute current diff" }, 500);
5779
6547
  }
5780
- const { fingerprintDiffFiles, diffSinceLast, filesToReviewSinceLast } = await import("./diff-fingerprint-BL5jNHnP.mjs");
6548
+ const { fingerprintDiffFiles, diffSinceLast, filesToReviewSinceLast } = await import("./diff-fingerprint-PnC8Fs1-.mjs");
5781
6549
  const delta = diffSinceLast(previous, fingerprintDiffFiles(currentPatch));
5782
6550
  return c.json({
5783
6551
  hasBaseline: true,
@@ -5961,6 +6729,152 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5961
6729
  app.get("/api/plan-review/status", (c) => {
5962
6730
  return c.json(planReviewSession.snapshot());
5963
6731
  });
6732
+ async function resolveDesignSystem(id, revision) {
6733
+ if (!id) return designSystems.getDefault();
6734
+ const system = await designSystems.get(id);
6735
+ if (!system) return null;
6736
+ if (revision && system.revisions?.length) {
6737
+ const snap = system.revisions.find((r) => r.revision === revision);
6738
+ if (snap) return {
6739
+ ...system,
6740
+ revision: snap.revision,
6741
+ status: snap.status,
6742
+ title: snap.title,
6743
+ tokens: snap.tokens,
6744
+ guidelines: snap.guidelines,
6745
+ components: snap.components,
6746
+ sources: snap.sources
6747
+ };
6748
+ }
6749
+ return system;
6750
+ }
6751
+ app.get("/api/design-systems", async (c) => {
6752
+ return c.json(await designSystems.getAll());
6753
+ });
6754
+ app.get("/api/design-systems/default", async (c) => {
6755
+ const system = await designSystems.getDefault();
6756
+ if (!system) return c.json({ error: "No design system" }, 404);
6757
+ return c.json(system);
6758
+ });
6759
+ app.get("/api/design-systems/:id", async (c) => {
6760
+ const system = await designSystems.get(c.req.param("id"));
6761
+ if (!system) return c.json({ error: "Design system not found" }, 404);
6762
+ return c.json(system);
6763
+ });
6764
+ app.post("/api/design-systems", async (c) => {
6765
+ const body = await c.req.json().catch(() => ({}));
6766
+ const system = await designSystems.upsert({
6767
+ id: typeof body.id === "string" ? body.id : DEFAULT_DESIGN_SYSTEM_ID,
6768
+ title: typeof body.title === "string" ? body.title : void 0,
6769
+ tokens: body.tokens,
6770
+ guidelines: typeof body.guidelines === "string" ? body.guidelines : void 0,
6771
+ sources: Array.isArray(body.sources) ? body.sources : void 0,
6772
+ status: body.status === "published" ? "published" : "draft"
6773
+ });
6774
+ broadcast("design-system", Date.now().toString());
6775
+ return c.json(system, 201);
6776
+ });
6777
+ app.put("/api/design-systems/:id", async (c) => {
6778
+ const body = await c.req.json().catch(() => ({}));
6779
+ if (!await designSystems.get(c.req.param("id"))) return c.json({ error: "Design system not found" }, 404);
6780
+ const system = await designSystems.propose(c.req.param("id"), {
6781
+ title: typeof body.title === "string" ? body.title : void 0,
6782
+ tokens: body.tokens,
6783
+ guidelines: typeof body.guidelines === "string" ? body.guidelines : void 0,
6784
+ components: Array.isArray(body.components) ? body.components : void 0,
6785
+ sources: Array.isArray(body.sources) ? body.sources : void 0
6786
+ });
6787
+ broadcast("design-system", Date.now().toString());
6788
+ return c.json(system);
6789
+ });
6790
+ app.post("/api/design-systems/:id/publish", async (c) => {
6791
+ const system = await designSystems.publish(c.req.param("id"));
6792
+ if (!system) return c.json({ error: "Design system not found" }, 404);
6793
+ broadcast("design-system", Date.now().toString());
6794
+ return c.json(system);
6795
+ });
6796
+ app.post("/api/design-systems/:id/extract", async (c) => {
6797
+ const body = await c.req.json().catch(() => ({}));
6798
+ const from = typeof body.from === "string" ? body.from : "css";
6799
+ let extracted;
6800
+ if (from === "text" && typeof body.text === "string") extracted = {
6801
+ tokens: extractTokensFromText(body.text),
6802
+ sources: [{
6803
+ kind: "css-vars",
6804
+ path: "paste"
6805
+ }],
6806
+ files: []
6807
+ };
6808
+ else if (from === "url" && typeof body.url === "string") {
6809
+ const url = body.url;
6810
+ if (!/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])/i.test(url)) return c.json({ error: "Capture URL must be a local http(s) address" }, 400);
6811
+ const res = await fetch(url).catch(() => null);
6812
+ const text = res && res.ok ? await res.text() : "";
6813
+ extracted = {
6814
+ tokens: extractTokensFromText(text),
6815
+ sources: [{
6816
+ kind: "url",
6817
+ url
6818
+ }],
6819
+ files: text ? [{
6820
+ path: url,
6821
+ count: Object.keys(extractTokensFromText(text).raw).length
6822
+ }] : []
6823
+ };
6824
+ } else extracted = await extractFromRepo(repoRoot);
6825
+ const id = c.req.param("id");
6826
+ const system = await designSystems.get(id) ? await designSystems.propose(id, {
6827
+ tokens: extracted.tokens,
6828
+ sources: extracted.sources
6829
+ }) : await designSystems.upsert({
6830
+ id,
6831
+ title: typeof body.title === "string" ? body.title : "Default",
6832
+ tokens: extracted.tokens,
6833
+ sources: extracted.sources,
6834
+ status: "draft"
6835
+ });
6836
+ broadcast("design-system", Date.now().toString());
6837
+ return c.json({
6838
+ system,
6839
+ extract: extracted
6840
+ });
6841
+ });
6842
+ app.post("/api/design-systems/:id/components", async (c) => {
6843
+ const body = await c.req.json().catch(() => ({}));
6844
+ if (typeof body.id !== "string" || typeof body.html !== "string") return c.json({ error: "id and html are required" }, 400);
6845
+ const system = await designSystems.addComponent(c.req.param("id"), {
6846
+ id: body.id,
6847
+ label: typeof body.label === "string" ? body.label : body.id,
6848
+ html: body.html,
6849
+ source: body.source === "promote" ? "promote" : "human"
6850
+ });
6851
+ if (!system) return c.json({ error: "Design system not found" }, 404);
6852
+ broadcast("design-system", Date.now().toString());
6853
+ return c.json(system);
6854
+ });
6855
+ app.post("/api/design-systems/:id/comments", async (c) => {
6856
+ const body = await c.req.json().catch(() => ({}));
6857
+ if (typeof body.body !== "string" || !body.body.trim()) return c.json({ error: "body is required" }, 400);
6858
+ const kind = body.kind === "token" || body.kind === "component" || body.kind === "guidelines" ? body.kind : "general";
6859
+ const system = await designSystems.addComment(c.req.param("id"), {
6860
+ kind,
6861
+ target: typeof body.target === "string" ? body.target : void 0,
6862
+ body: body.body
6863
+ });
6864
+ if (!system) return c.json({ error: "Design system not found" }, 404);
6865
+ broadcast("design-system", Date.now().toString());
6866
+ return c.json(system);
6867
+ });
6868
+ app.put("/api/design-systems/:id/comments/:commentId", async (c) => {
6869
+ const body = await c.req.json().catch(() => ({}));
6870
+ const system = await designSystems.updateComment(c.req.param("id"), c.req.param("commentId"), {
6871
+ body: typeof body.body === "string" ? body.body : void 0,
6872
+ status: body.status === "resolved" || body.status === "open" ? body.status : void 0
6873
+ });
6874
+ if (!system) return c.json({ error: "Not found" }, 404);
6875
+ broadcast("design-system", Date.now().toString());
6876
+ return c.json(system);
6877
+ });
5964
6878
  const summarizeMockup = (mockup, includeComments = false) => ({
5965
6879
  id: mockup.id,
5966
6880
  title: mockup.title,
@@ -5981,6 +6895,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
5981
6895
  open: mockup.comments.filter((comment) => comment.status === "open").length,
5982
6896
  resolved: mockup.comments.filter((comment) => comment.status === "resolved").length
5983
6897
  },
6898
+ designSystemId: mockup.designSystemId,
6899
+ planId: mockup.planId,
5984
6900
  ...includeComments ? { comments: mockup.comments } : {}
5985
6901
  });
5986
6902
  app.get("/api/mockups", async (c) => {
@@ -6045,7 +6961,17 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6045
6961
  version: versionN,
6046
6962
  viewport
6047
6963
  });
6048
- return c.html(injectMockupProbe(screen.html, {
6964
+ const snapBinding = snap;
6965
+ const mode = snapBinding.mode ?? mockup.mode ?? "document";
6966
+ const theme = c.req.query("theme") === "dark" ? "dark" : "light";
6967
+ const system = await resolveDesignSystem(snapBinding.designSystemId ?? mockup.designSystemId, snapBinding.designRevision ?? mockup.designRevision);
6968
+ const rendered = renderMockupHtml(screen.html, {
6969
+ mode,
6970
+ system,
6971
+ title: mockup.title,
6972
+ theme
6973
+ });
6974
+ return c.html(injectMockupProbe(rendered, {
6049
6975
  nonce,
6050
6976
  viewport,
6051
6977
  passive
@@ -6060,7 +6986,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6060
6986
  const mockup = await mockups.get(c.req.param("id"));
6061
6987
  if (!mockup) return c.json({ error: "Mockup not found" }, 404);
6062
6988
  const view = c.req.query("view") || "summary";
6063
- if (view !== "summary" && view !== "comments" && view !== "comment" && view !== "screen") return c.json({ error: "view must be summary|comments|comment|screen" }, 400);
6989
+ if (view !== "summary" && view !== "comments" && view !== "comment" && view !== "screen" && view !== "preview") return c.json({ error: "view must be summary|comments|comment|screen|preview" }, 400);
6064
6990
  const status = c.req.query("status");
6065
6991
  if (status !== void 0 && status !== "open" && status !== "resolved") return c.json({ error: "status must be open or resolved" }, 400);
6066
6992
  const screenFilter = c.req.query("screen");
@@ -6108,6 +7034,26 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6108
7034
  }
6109
7035
  });
6110
7036
  }
7037
+ if (view === "preview") {
7038
+ const screenId = c.req.query("screen") || mockup.screens[0]?.id;
7039
+ const screen = mockup.screens.find((s) => s.id === screenId);
7040
+ if (!screen) return c.json({ error: "Screen not found" }, 404);
7041
+ const viewport = normalizeMockupViewport(c.req.query("viewport"));
7042
+ const theme = c.req.query("theme") === "dark" ? "dark" : "light";
7043
+ const system = await resolveDesignSystem(mockup.designSystemId, mockup.designRevision);
7044
+ const preview = await renderMockupPreview(renderMockupHtml(screen.html, {
7045
+ mode: mockup.mode ?? "document",
7046
+ system,
7047
+ title: mockup.title,
7048
+ theme
7049
+ }), { viewport });
7050
+ return c.json({
7051
+ view: "preview",
7052
+ mockupId: mockup.id,
7053
+ screenId: screen.id,
7054
+ ...preview
7055
+ });
7056
+ }
6111
7057
  if (view === "comment") {
6112
7058
  const id = c.req.query("id");
6113
7059
  if (!id) return c.json({ error: "id is required for view=comment" }, 400);
@@ -6186,6 +7132,10 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6186
7132
  error: result.error,
6187
7133
  code: "exact-text-not-found"
6188
7134
  }, 409);
7135
+ if (result.error?.includes("not found") && result.error.startsWith("Region ")) return c.json({
7136
+ error: result.error,
7137
+ code: "region-not-found"
7138
+ }, 409);
6189
7139
  if (result.error === "Mockup not found" || result.error?.endsWith("not found")) return c.json({ error: result.error }, 404);
6190
7140
  return c.json({ error: result.error ?? "Screen operation failed" }, 400);
6191
7141
  };
@@ -6217,14 +7167,26 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6217
7167
  const mockupId = c.req.param("id");
6218
7168
  const screenId = c.req.param("screenId");
6219
7169
  const body = await c.req.json().catch(() => ({}));
7170
+ const expectedVersion = Number.isFinite(body.expectedVersion) ? Number(body.expectedVersion) : void 0;
7171
+ const region = typeof body.region === "string" ? body.region.trim() : "";
7172
+ if (region) {
7173
+ if (typeof body.replacement !== "string") return c.json({ error: "replacement is required" }, 400);
7174
+ const result = await mockups.replaceRegion(mockupId, screenId, {
7175
+ region,
7176
+ replacement: body.replacement
7177
+ }, { expectedVersion });
7178
+ if (!result.mockup) return screenOpError(c, result);
7179
+ return c.json({
7180
+ mockup: result.mockup,
7181
+ occurrences: result.occurrences
7182
+ }, 200);
7183
+ }
6220
7184
  const expectedText = typeof body.expectedText === "string" ? body.expectedText : "";
6221
- const replacement = typeof body.replacement === "string" ? body.replacement : "";
6222
- if (!expectedText) return c.json({ error: "expectedText is required" }, 400);
7185
+ if (!expectedText) return c.json({ error: "expectedText or region is required" }, 400);
6223
7186
  if (typeof body.replacement !== "string") return c.json({ error: "replacement is required" }, 400);
6224
- const expectedVersion = Number.isFinite(body.expectedVersion) ? Number(body.expectedVersion) : void 0;
6225
7187
  const result = await mockups.patchScreen(mockupId, screenId, {
6226
7188
  expectedText,
6227
- replacement
7189
+ replacement: body.replacement
6228
7190
  }, { expectedVersion });
6229
7191
  if (!result.mockup) return screenOpError(c, result);
6230
7192
  return c.json({
@@ -6262,15 +7224,38 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6262
7224
  });
6263
7225
  app.post("/api/mockups", async (c) => {
6264
7226
  const body = await c.req.json().catch(() => ({}));
7227
+ if (typeof body.fromMockupId === "string") {
7228
+ const source = await mockups.get(body.fromMockupId);
7229
+ if (!source) return c.json({ error: "Source mockup not found" }, 404);
7230
+ body.screens = source.screens.map((s) => ({
7231
+ id: s.id,
7232
+ label: s.label,
7233
+ html: s.html,
7234
+ stateOf: s.stateOf,
7235
+ flow: s.flow
7236
+ }));
7237
+ if (!body.designSystemId && !body.designSystem) body.designSystemId = source.designSystemId;
7238
+ if (!body.mode) body.mode = source.mode;
7239
+ if (!body.flows) body.flows = source.flows;
7240
+ } else if (body.blank === true && !body.html && !body.screens) body.html = "";
7241
+ if (body.blank === true && body.html === "") body.html = "<!-- empty -->";
6265
7242
  const normalized = screensFromSubmitBody(body);
6266
7243
  if (!normalized.ok) return c.json({ error: normalized.error }, 400);
6267
7244
  const title = typeof body.title === "string" && body.title.trim() ? body.title.trim() : "Untitled mockup";
7245
+ const requestedMode = body.mode;
7246
+ const system = await resolveDesignSystem(typeof body.designSystem === "string" ? body.designSystem : typeof body.designSystemId === "string" ? body.designSystemId : void 0);
7247
+ const mode = resolveRenderMode(requestedMode, system);
6268
7248
  const mockup = await mockups.upsert({
6269
7249
  id: typeof body.id === "string" && body.id ? body.id : void 0,
6270
7250
  title,
6271
7251
  screens: normalized.screens,
6272
7252
  source: typeof body.source === "string" ? body.source : void 0,
6273
- model: typeof body.model === "string" ? body.model : void 0
7253
+ model: typeof body.model === "string" ? body.model : void 0,
7254
+ designSystemId: system?.id,
7255
+ designRevision: system?.revision,
7256
+ mode,
7257
+ planId: typeof body.planId === "string" ? body.planId : void 0,
7258
+ flows: Array.isArray(body.flows) ? body.flows : void 0
6274
7259
  });
6275
7260
  const hints = lintMockupScreens(normalized.screens);
6276
7261
  return c.json(hints.length > 0 ? {
@@ -6296,6 +7281,37 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6296
7281
  if (!updated) return c.json({ error: "Mockup not found" }, 404);
6297
7282
  return c.json(updated);
6298
7283
  });
7284
+ app.get("/api/mockups/:id/handoff", async (c) => {
7285
+ const mockup = await mockups.get(c.req.param("id"));
7286
+ if (!mockup) return c.json({ error: "Mockup not found" }, 404);
7287
+ const handoff = buildMockupHandoff(mockup, await resolveDesignSystem(mockup.designSystemId, mockup.designRevision));
7288
+ return c.json({
7289
+ ...handoff,
7290
+ xml: formatMockupHandoffXml(handoff)
7291
+ });
7292
+ });
7293
+ app.post("/api/mockups/:id/comments/:commentId/apply-suggestion", async (c) => {
7294
+ const mockup = await mockups.get(c.req.param("id"));
7295
+ if (!mockup) return c.json({ error: "Mockup not found" }, 404);
7296
+ const comment = mockup.comments.find((x) => x.id === c.req.param("commentId"));
7297
+ if (!comment) return c.json({ error: "Comment not found" }, 404);
7298
+ const suggestion = extractSuggestion(comment.body);
7299
+ if (!suggestion) return c.json({ error: "No ```suggestion block in comment" }, 400);
7300
+ const reqBody = await c.req.json().catch(() => ({}));
7301
+ const expectedVersion = Number.isFinite(reqBody.expectedVersion) ? Number(reqBody.expectedVersion) : mockup.version;
7302
+ let result;
7303
+ if (comment.target) result = await mockups.replaceRegion(mockup.id, comment.screenId, {
7304
+ region: comment.target,
7305
+ replacement: suggestion
7306
+ }, { expectedVersion });
7307
+ else if (comment.html) result = await mockups.patchScreen(mockup.id, comment.screenId, {
7308
+ expectedText: comment.html,
7309
+ replacement: suggestion
7310
+ }, { expectedVersion });
7311
+ else return c.json({ error: "Comment has no target or html to replace" }, 400);
7312
+ if (!result.mockup) return screenOpError(c, result);
7313
+ return c.json(result.mockup);
7314
+ });
6299
7315
  app.delete("/api/mockups/:id", async (c) => {
6300
7316
  if (!await mockups.remove(c.req.param("id"))) return c.json({ error: "Mockup not found" }, 404);
6301
7317
  return c.json({ ok: true });
@@ -6318,6 +7334,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6318
7334
  }
6319
7335
  if (typeof body.body !== "string" || !body.body.trim()) return c.json({ error: "body is required" }, 400);
6320
7336
  const viewport = normalizeMockupViewport(body.viewport);
7337
+ const theme = body.theme === "dark" ? "dark" : "light";
6321
7338
  const severityRaw = body.severity;
6322
7339
  const severity = severityRaw === "blocking" || severityRaw === "nit" || severityRaw === "question" || severityRaw === "praise" || severityRaw === "none" ? severityRaw : void 0;
6323
7340
  const rect = body.rect && typeof body.rect === "object" ? body.rect : void 0;
@@ -6346,6 +7363,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
6346
7363
  createdAt: Date.now(),
6347
7364
  createdAtMockupVersion: Number.isFinite(body.createdAtMockupVersion) ? Number(body.createdAtMockupVersion) : mockup.version,
6348
7365
  viewport,
7366
+ theme,
6349
7367
  replies: [],
6350
7368
  ...severity && severity !== "none" ? { severity } : {}
6351
7369
  };