specrails-desktop 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/dist/assets/ActivityFeedPage-DJJlZ3mF.js +1 -0
- package/client/dist/assets/AgentsPage-49JaEDjR.js +86 -0
- package/client/dist/assets/{AnalyticsPage-Dyyz1ht3.js → AnalyticsPage-BUd3gWYC.js} +1 -1
- package/client/dist/assets/{BarChart-CMdLa6Es.js → BarChart-HDe_YoUD.js} +2 -2
- package/client/dist/assets/CodePage-CqPPND47.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-CTNwb639.js → DesktopAnalyticsPage-CgvmSvF0.js} +1 -1
- package/client/dist/assets/DocsDialog-hHFd3Ejs.js +11 -0
- package/client/dist/assets/DocsPage-B4R1aksg.js +11 -0
- package/client/dist/assets/{ExportDropdown-DuoZcdYN.js → ExportDropdown-f4dwQjlT.js} +1 -1
- package/client/dist/assets/IntegrationsPage-CX2Ybxx0.js +3 -0
- package/client/dist/assets/JobDetailPage-DN2Jc8Ti.js +16 -0
- package/client/dist/assets/JobsPage-DmdpqijT.js +1 -0
- package/client/dist/assets/{cssMode-Cc6ozl-J.js → cssMode-DzNPAYFh.js} +1 -1
- package/client/dist/assets/{dist-js-H6hyhSuv.js → dist-js-COfIfLRE.js} +1 -1
- package/client/dist/assets/{dist-js-4UEGaKhD.js → dist-js-CvScGQU_.js} +1 -1
- package/client/dist/assets/{editor.main-CfXxHimg.js → editor.main-C7Rmw-hR.js} +2 -2
- package/client/dist/assets/{freemarker2-DP7J1gG3.js → freemarker2-Cszs4SVo.js} +1 -1
- package/client/dist/assets/{handlebars-BjRlucw6.js → handlebars-Dp7Lsuym.js} +1 -1
- package/client/dist/assets/{html-OumBQJ-U.js → html-BURidrEm.js} +1 -1
- package/client/dist/assets/{htmlMode-CStc3zXM.js → htmlMode--k5M7GjZ.js} +1 -1
- package/client/dist/assets/index-DBpvYrDK.css +2 -0
- package/client/dist/assets/index-DGIXKRHE.js +142 -0
- package/client/dist/assets/{integrations-Cublz3m6.js → integrations-2C7MkGT0.js} +1 -1
- package/client/dist/assets/{integrations-HIlUxXVs.js → integrations-BDC670cg.js} +1 -1
- package/client/dist/assets/integrations-BqUmRUef.js +1 -0
- package/client/dist/assets/{integrations-DmQYCUvN.js → integrations-C2jQtv-s.js} +1 -1
- package/client/dist/assets/{integrations-DRdbki5W.js → integrations-CB98NeH5.js} +1 -1
- package/client/dist/assets/{integrations-C3p12Ms6.js → integrations-CX4p_bij.js} +1 -1
- package/client/dist/assets/{integrations-DaC4SzzL.js → integrations-_SuVeQIG.js} +1 -1
- package/client/dist/assets/{integrations-Cr6hH7XR.js → integrations-eQPHAYsE.js} +1 -1
- package/client/dist/assets/{javascript-CMk--e7g.js → javascript-kJQz__44.js} +1 -1
- package/client/dist/assets/jira-C-ATCti0.js +1 -0
- package/client/dist/assets/jira-CmVfRM-b.js +1 -0
- package/client/dist/assets/jira-D7bkKAX8.js +1 -0
- package/client/dist/assets/jira-DKImM1YH.js +1 -0
- package/client/dist/assets/jira-DOw8bkIR.js +1 -0
- package/client/dist/assets/jira-DlA-wGp-.js +1 -0
- package/client/dist/assets/jira-Fob8EGxN.js +1 -0
- package/client/dist/assets/jira-xZA2lixb.js +1 -0
- package/client/dist/assets/{jsonMode-C2h3ZcjZ.js → jsonMode-v5JYPpnz.js} +1 -1
- package/client/dist/assets/{lib-Cs5FrUJI.js → lib-Bro9Z0gp.js} +1 -1
- package/client/dist/assets/{liquid-mI3KJrBE.js → liquid-Dl9I6gWt.js} +1 -1
- package/client/dist/assets/{lspLanguageFeatures-DU09ggWi.js → lspLanguageFeatures-CPlEe0NK.js} +1 -1
- package/client/dist/assets/{mdx-C41VDTR_.js → mdx-Byl7TtzQ.js} +1 -1
- package/client/dist/assets/{monaco.contribution-CPObAXMC.js → monaco.contribution-YMAkHQcQ.js} +2 -2
- package/client/dist/assets/{python-Y27rKQtk.js → python-jWQwT6j2.js} +1 -1
- package/client/dist/assets/{razor-Cd5-q9Bp.js → razor-BWS3sP-E.js} +1 -1
- package/client/dist/assets/setup-C0dzw8j4.js +1 -0
- package/client/dist/assets/setup-C1IA-9YS.js +1 -0
- package/client/dist/assets/setup-CpfjaNut.js +1 -0
- package/client/dist/assets/setup-D3rNZA9A.js +1 -0
- package/client/dist/assets/setup-UD2aanGs.js +1 -0
- package/client/dist/assets/setup-WP6WOYQh.js +1 -0
- package/client/dist/assets/setup-gzLG8T6F.js +1 -0
- package/client/dist/assets/setup-pjgmYHx6.js +1 -0
- package/client/dist/assets/specs-4lA_u79w.js +1 -0
- package/client/dist/assets/{specs-D2FzlLn9.js → specs-BHjxcjOf.js} +1 -1
- package/client/dist/assets/{specs-CZ1PsXsC.js → specs-CXNQzPk9.js} +1 -1
- package/client/dist/assets/{specs-Dyc5hYeE.js → specs-DFnc2Huj.js} +1 -1
- package/client/dist/assets/{specs-BFfu3u-a.js → specs-DZCLH2-l.js} +1 -1
- package/client/dist/assets/{specs-B__C8-8a.js → specs-DgmyAE3N.js} +1 -1
- package/client/dist/assets/{specs-DaUTrNF9.js → specs-DicWhvwi.js} +1 -1
- package/client/dist/assets/{specs-k0PyLDVt.js → specs-dkro6lSM.js} +1 -1
- package/client/dist/assets/{tsMode-B0y_xEci.js → tsMode-BbOGOuSV.js} +1 -1
- package/client/dist/assets/{typescript-BzK0OgwW.js → typescript-eBtFQJLs.js} +1 -1
- package/client/dist/assets/{useProjectCache-BZWYV-w-.js → useProjectCache-D9juBhsO.js} +1 -1
- package/client/dist/assets/{workers-rt--R2Qy.js → workers-BvicOoDf.js} +1 -1
- package/client/dist/assets/{xml-eX9QXAmI.js → xml-BJepAPyM.js} +1 -1
- package/client/dist/assets/{yaml-fcsNkpOt.js → yaml-DabgV-eA.js} +1 -1
- package/client/dist/index.html +12 -11
- package/docs/jira-integration-plan.md +321 -0
- package/package.json +1 -1
- package/server/dist/db.js +80 -0
- package/server/dist/feature-flags.js +11 -0
- package/server/dist/jira/jira-adf.js +113 -0
- package/server/dist/jira/jira-backlog-config.js +58 -0
- package/server/dist/jira/jira-client.js +279 -0
- package/server/dist/jira/jira-credential-store.js +103 -0
- package/server/dist/jira/jira-db.js +341 -0
- package/server/dist/jira/jira-issue-fields.js +428 -0
- package/server/dist/jira/jira-materializer.js +250 -0
- package/server/dist/jira/jira-status-resolver.js +211 -0
- package/server/dist/jira/jira-sync-manager.js +1014 -0
- package/server/dist/jira/types.js +9 -0
- package/server/dist/jira-router.js +304 -0
- package/server/dist/project-registry.js +43 -1
- package/server/dist/project-router-tickets.js +49 -1
- package/server/dist/project-router.js +4 -0
- package/server/dist/rails-router.js +12 -0
- package/client/dist/assets/ActivityFeedPage-3Veccrvk.js +0 -1
- package/client/dist/assets/AgentsPage-2mFPghP4.js +0 -86
- package/client/dist/assets/CodePage-D7Xwjhut.js +0 -2
- package/client/dist/assets/DocsDialog-D8yoyZDD.js +0 -11
- package/client/dist/assets/DocsPage-CeO-fAxy.js +0 -11
- package/client/dist/assets/IntegrationsPage-iIZ0UEzf.js +0 -3
- package/client/dist/assets/JobDetailPage-DgJHAH2m.js +0 -16
- package/client/dist/assets/JobsPage-Bv_RpRAE.js +0 -1
- package/client/dist/assets/index-CGHKpC-N.js +0 -142
- package/client/dist/assets/index-D17R4Cjc.css +0 -2
- package/client/dist/assets/integrations-D28q1kF6.js +0 -1
- package/client/dist/assets/setup--FMCsnQS.js +0 -1
- package/client/dist/assets/setup-B19ZpBNi.js +0 -1
- package/client/dist/assets/setup-BZPmkjSN.js +0 -1
- package/client/dist/assets/setup-BqYA02rS.js +0 -1
- package/client/dist/assets/setup-ChKQDHN9.js +0 -1
- package/client/dist/assets/setup-D2n9jMfM.js +0 -1
- package/client/dist/assets/setup-P3r6YP1D.js +0 -1
- package/client/dist/assets/setup-fnfEbwlv.js +0 -1
- package/client/dist/assets/specs-cKEh2LXt.js +0 -1
- /package/client/dist/assets/{abap-Bw6f2wDG.js → abap-s65oMlhi.js} +0 -0
- /package/client/dist/assets/{activity-BdrPln96.js → activity-BqqwnH_h.js} +0 -0
- /package/client/dist/assets/{activity-BEIp_Y1A.js → activity-C8qqEIoP.js} +0 -0
- /package/client/dist/assets/{activity-CpkRS8Sx.js → activity-CZVM4nlJ.js} +0 -0
- /package/client/dist/assets/{activity-DOUVEjJi.js → activity-Cyy07Tgo.js} +0 -0
- /package/client/dist/assets/{activity-DRwkql_y.js → activity-DlbWCa4y.js} +0 -0
- /package/client/dist/assets/{activity-DKCpESPt.js → activity-Dwq0heud.js} +0 -0
- /package/client/dist/assets/{activity-DcDQ7tjw.js → activity-qFTcMyW9.js} +0 -0
- /package/client/dist/assets/{addon-image-3WCl5Vhd.js → addon-image-CpF0L0jM.js} +0 -0
- /package/client/dist/assets/{addon-ligatures-C5OdliKs.js → addon-ligatures-hXysGZrA.js} +0 -0
- /package/client/dist/assets/{addon-webgl-BbX6pSjl.js → addon-webgl-Cn1slavz.js} +0 -0
- /package/client/dist/assets/{addspec-D33ocMxf.js → addspec-B1FTtI2a.js} +0 -0
- /package/client/dist/assets/{addspec-DFswZ0jK.js → addspec-BCT9vm_c.js} +0 -0
- /package/client/dist/assets/{addspec-DVZ15Jp8.js → addspec-DeDOztDr.js} +0 -0
- /package/client/dist/assets/{addspec-Fkv91Opc.js → addspec-DpRgmfmx.js} +0 -0
- /package/client/dist/assets/{addspec-BEeF5-zc.js → addspec-Dw-0Dg-4.js} +0 -0
- /package/client/dist/assets/{addspec-B5yl4Loj.js → addspec-rp496P_F.js} +0 -0
- /package/client/dist/assets/{addspec-DRE-jZv7.js → addspec-v8j6A7CD.js} +0 -0
- /package/client/dist/assets/{agents-DK-Dlc0i.js → agents-23iPejcA.js} +0 -0
- /package/client/dist/assets/{agents-Q6Ldfpxx.js → agents-BDx1RXcl.js} +0 -0
- /package/client/dist/assets/{agents-TeOSy-ax.js → agents-BFr3kUhK.js} +0 -0
- /package/client/dist/assets/{agents-Bm9rPqnt.js → agents-B_1L9xRg.js} +0 -0
- /package/client/dist/assets/{agents-1nCDWRmP.js → agents-BlPnx-mz.js} +0 -0
- /package/client/dist/assets/{agents-iTqjRajS.js → agents-DcxZHzNr.js} +0 -0
- /package/client/dist/assets/{agents-s87sMGzL.js → agents-G3shOewU.js} +0 -0
- /package/client/dist/assets/{agentstudio-B6Wb59E7.js → agentstudio-B-CMAQqy.js} +0 -0
- /package/client/dist/assets/{agentstudio-D3I62TLJ.js → agentstudio-Bk1eZcv4.js} +0 -0
- /package/client/dist/assets/{agentstudio-DuH9TogZ.js → agentstudio-ChxNuGAu.js} +0 -0
- /package/client/dist/assets/{agentstudio-Kw88_dUF.js → agentstudio-DNlme601.js} +0 -0
- /package/client/dist/assets/{agentstudio-BdidyBzZ.js → agentstudio-DpP9caEE.js} +0 -0
- /package/client/dist/assets/{agentstudio-BSnWLR63.js → agentstudio-Y3G0ddJ2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BADhZ41e.js → agentstudio-kk9RB7Se.js} +0 -0
- /package/client/dist/assets/{aiedit-DJMny-D5.js → aiedit-5ETerMK1.js} +0 -0
- /package/client/dist/assets/{aiedit-D2ji6Qy0.js → aiedit-BBCrOpHq.js} +0 -0
- /package/client/dist/assets/{aiedit-DAhZTvtk.js → aiedit-BMtcGYNE.js} +0 -0
- /package/client/dist/assets/{aiedit-DvrcbwGv.js → aiedit-D9ddlgkM.js} +0 -0
- /package/client/dist/assets/{aiedit-WBSjT_C1.js → aiedit-De0SOH6S.js} +0 -0
- /package/client/dist/assets/{aiedit-BWxHGsYA.js → aiedit-DrfzQroF.js} +0 -0
- /package/client/dist/assets/{aiedit-DOcxERkU.js → aiedit-fMltW101.js} +0 -0
- /package/client/dist/assets/{analytics-C9Zc-rkM.js → analytics-BeTyviO8.js} +0 -0
- /package/client/dist/assets/{analytics-CrPCZRJ-.js → analytics-C4eEO260.js} +0 -0
- /package/client/dist/assets/{analytics-CYj0tfj7.js → analytics-C67cIA1b.js} +0 -0
- /package/client/dist/assets/{analytics-C6EzgtdE.js → analytics-CAguvW28.js} +0 -0
- /package/client/dist/assets/{analytics-CVx3YOc0.js → analytics-DBtt8Mgk.js} +0 -0
- /package/client/dist/assets/{analytics-CnY4kNG3.js → analytics-DUPtODxX.js} +0 -0
- /package/client/dist/assets/{analytics-BIdr0YfL.js → analytics-YIpQvPAc.js} +0 -0
- /package/client/dist/assets/{apex-Cw8_REBo.js → apex-BLUBIldB.js} +0 -0
- /package/client/dist/assets/{attachments-DYHGA2Dj.js → attachments-CCWasu-P.js} +0 -0
- /package/client/dist/assets/{attachments-Dd92KpUH.js → attachments-CHaDUfjB.js} +0 -0
- /package/client/dist/assets/{attachments-DzdU6DV6.js → attachments-CVSAbGNl.js} +0 -0
- /package/client/dist/assets/{attachments-Bcf6BG6V.js → attachments-Chg5poG1.js} +0 -0
- /package/client/dist/assets/{attachments-BW4L3l2L.js → attachments-DazTVJbH.js} +0 -0
- /package/client/dist/assets/{attachments-COcrGRFz.js → attachments-Dn-JImAK.js} +0 -0
- /package/client/dist/assets/{attachments-Bke8sCU4.js → attachments-LDA9kp2X.js} +0 -0
- /package/client/dist/assets/{azcli-Cz6HAoOw.js → azcli-DuWxh9mO.js} +0 -0
- /package/client/dist/assets/{bat-CcJ-xyqL.js → bat-UKoTejQm.js} +0 -0
- /package/client/dist/assets/{bicep-z1WDCKYz.js → bicep-4sTT4B3D.js} +0 -0
- /package/client/dist/assets/{browser-DGITz3fC.js → browser-BDd1dbFa.js} +0 -0
- /package/client/dist/assets/{browser-JsAIGCEW.js → browser-BWSgbfdX.js} +0 -0
- /package/client/dist/assets/{browser-M5-rbPlw.js → browser-D2Y_UAKA.js} +0 -0
- /package/client/dist/assets/{browser-BlYF4OOq.js → browser-DH9SGVfM.js} +0 -0
- /package/client/dist/assets/{browser-Bc-YdlVg.js → browser-DWOVYMlg.js} +0 -0
- /package/client/dist/assets/{browser-CT-ReZGt.js → browser-Dxc_VIRK.js} +0 -0
- /package/client/dist/assets/{browser-5ErDlJoR.js → browser-lTQwcDCI.js} +0 -0
- /package/client/dist/assets/{cameligo-BRewOpfa.js → cameligo-CAAryRYO.js} +0 -0
- /package/client/dist/assets/{chat-DwUm6W9z.js → chat-BO9MvVID.js} +0 -0
- /package/client/dist/assets/{chat-BEGuC03z.js → chat-CPgmgZOj.js} +0 -0
- /package/client/dist/assets/{chat-CboQguCi.js → chat-CUrG1eVg.js} +0 -0
- /package/client/dist/assets/{chat-DRCa9pOt.js → chat-CvOOKB2s.js} +0 -0
- /package/client/dist/assets/{chat-BEW60P_u.js → chat-DIh3hr6y.js} +0 -0
- /package/client/dist/assets/{chat-yoXwguQu.js → chat-UVVZqA57.js} +0 -0
- /package/client/dist/assets/{chat-BQNMD0PL.js → chat-mPn3UlMl.js} +0 -0
- /package/client/dist/assets/{clojure-DBjRWN6g.js → clojure-BlMERO1w.js} +0 -0
- /package/client/dist/assets/{clsx-DnqN-uhr.js → clsx-CnH-HMk3.js} +0 -0
- /package/client/dist/assets/{code-zCwBt3Uu.js → code-BwIz8agY.js} +0 -0
- /package/client/dist/assets/{code-g0qFMzyg.js → code-CD7yNSK0.js} +0 -0
- /package/client/dist/assets/{code-DDU0CRS0.js → code-CDFlxUFC.js} +0 -0
- /package/client/dist/assets/{code-L35Loak_.js → code-Cp3Fdng-.js} +0 -0
- /package/client/dist/assets/{code-D1z-YDt-.js → code-D24e1Crx.js} +0 -0
- /package/client/dist/assets/{code-BtsmPQLV.js → code-DtZBQTi9.js} +0 -0
- /package/client/dist/assets/{code-Coa8f2Sh.js → code-nKa0fkm_.js} +0 -0
- /package/client/dist/assets/{coffee-Cfk_XHGR.js → coffee-Cj8D-Wl1.js} +0 -0
- /package/client/dist/assets/{commands-sqrqsxyE.js → commands-B-MVT-2F.js} +0 -0
- /package/client/dist/assets/{commands-UD1NzmwX.js → commands-B0yFTp7e.js} +0 -0
- /package/client/dist/assets/{commands-DLrvnPNg.js → commands-BR1kDkHQ.js} +0 -0
- /package/client/dist/assets/{commands-CJxCry-o.js → commands-Cb21pDlG.js} +0 -0
- /package/client/dist/assets/{commands-CfgY-_of.js → commands-DWgp-8W1.js} +0 -0
- /package/client/dist/assets/{commands-B772IyDa.js → commands-ddsl1V91.js} +0 -0
- /package/client/dist/assets/{commands-BDDp6xFG.js → commands-t4frzhB0.js} +0 -0
- /package/client/dist/assets/{common-Dmm1GhdD.js → common-5ilvMOcH.js} +0 -0
- /package/client/dist/assets/{common-DltqHaAe.js → common-B4sqsKp7.js} +0 -0
- /package/client/dist/assets/{common-GbpxfPG8.js → common-BKpVwUIf.js} +0 -0
- /package/client/dist/assets/{common-DeDELLZJ.js → common-BzEC3kJU.js} +0 -0
- /package/client/dist/assets/{common-DnjcgkPH.js → common-CALKUpYm.js} +0 -0
- /package/client/dist/assets/{common-Dard9UNH.js → common-CTEbWVZS.js} +0 -0
- /package/client/dist/assets/{common-DCr6VzJ7.js → common-DQiza2Xp.js} +0 -0
- /package/client/dist/assets/{cpp-BVob6BaP.js → cpp-BPfKnaj_.js} +0 -0
- /package/client/dist/assets/{csharp-C4fbRuOu.js → csharp-gX-x5uD6.js} +0 -0
- /package/client/dist/assets/{csp-DthFP_vT.js → csp-DKGVt8SM.js} +0 -0
- /package/client/dist/assets/{css-CGMH0hcW.js → css-CPMdnAVq.js} +0 -0
- /package/client/dist/assets/{cypher-Pnf68BRV.js → cypher-ClMDrj9S.js} +0 -0
- /package/client/dist/assets/{dart-PMMOtxZX.js → dart-C4zbzpVv.js} +0 -0
- /package/client/dist/assets/{dashboard-BZBADHSj.js → dashboard--Y6yzMlf.js} +0 -0
- /package/client/dist/assets/{dashboard-I19DXBxw.js → dashboard--a4-6oYE.js} +0 -0
- /package/client/dist/assets/{dashboard-CB6Le1yN.js → dashboard-BiJ3CDTG.js} +0 -0
- /package/client/dist/assets/{dashboard-B4ixDVk8.js → dashboard-CiXjk63Z.js} +0 -0
- /package/client/dist/assets/{dashboard-C1MfeUHs.js → dashboard-Cx5VjCea.js} +0 -0
- /package/client/dist/assets/{dashboard-C7SK6xu5.js → dashboard-D7jg25XR.js} +0 -0
- /package/client/dist/assets/{dashboard-CoTpMOBM.js → dashboard-DpGYK2s1.js} +0 -0
- /package/client/dist/assets/{dockerfile-di1nsJCc.js → dockerfile-D9xw73D1.js} +0 -0
- /package/client/dist/assets/{ecl-D_WVcB5M.js → ecl-gqO8tIR9.js} +0 -0
- /package/client/dist/assets/{editor.api2-XLGzZfbc.js → editor.api2-BPnIxMjz.js} +0 -0
- /package/client/dist/assets/{elixir-OAdJEMOn.js → elixir-DSAhVF3_.js} +0 -0
- /package/client/dist/assets/{explore-D2EFgt8J.js → explore-BE5UmlbD.js} +0 -0
- /package/client/dist/assets/{explore-BV5Xxlsn.js → explore-BmTaI8dX.js} +0 -0
- /package/client/dist/assets/{explore-A8Ltoblq.js → explore-CCwkqoWq.js} +0 -0
- /package/client/dist/assets/{explore-4mFpnrKU.js → explore-CMdEoPDx.js} +0 -0
- /package/client/dist/assets/{explore-C3FSE42C.js → explore-CtdCL4QU.js} +0 -0
- /package/client/dist/assets/{explore-B9A3iN2W.js → explore-DHjxSkqQ.js} +0 -0
- /package/client/dist/assets/{explore-BrBJvfjP.js → explore-__BeALjE.js} +0 -0
- /package/client/dist/assets/{flow9-D3QEZjgn.js → flow9-DeQCSPOd.js} +0 -0
- /package/client/dist/assets/{format-command-CwGuwzGA.js → format-command-2VNoNnMv.js} +0 -0
- /package/client/dist/assets/{fsharp-BF0k_8N8.js → fsharp-CEfaXL-S.js} +0 -0
- /package/client/dist/assets/{go-BAQO5Jsz.js → go-Xp1OkZCh.js} +0 -0
- /package/client/dist/assets/{graphql-hdFVFkiV.js → graphql-BwRXrUwe.js} +0 -0
- /package/client/dist/assets/{hcl-DWnl1o-X.js → hcl-u06DtVFk.js} +0 -0
- /package/client/dist/assets/{ini-CB-6OVu3.js → ini-AmeIpFND.js} +0 -0
- /package/client/dist/assets/{java-d1CmfiHX.js → java-CyDbRQjX.js} +0 -0
- /package/client/dist/assets/{jobs-DPPT6bV6.js → jobs-8viuHLDV.js} +0 -0
- /package/client/dist/assets/{jobs-3j3_npyo.js → jobs-AW2eB5D-.js} +0 -0
- /package/client/dist/assets/{jobs-2N3RXDAM.js → jobs-BSm89DL5.js} +0 -0
- /package/client/dist/assets/{jobs-BqEbCCxD.js → jobs-BZ3sQHjZ.js} +0 -0
- /package/client/dist/assets/{jobs-cHYInoau.js → jobs-Bd8AdOTb.js} +0 -0
- /package/client/dist/assets/{jobs-DRzjWI9u.js → jobs-CRtsq_u0.js} +0 -0
- /package/client/dist/assets/{jobs-2f6Hdc72.js → jobs-CSRwFQ6K.js} +0 -0
- /package/client/dist/assets/{jobs-vGfzIDQa.js → jobs-CbEl7WMI.js} +0 -0
- /package/client/dist/assets/{julia-Bgv08lKa.js → julia-BqialFRG.js} +0 -0
- /package/client/dist/assets/{kotlin-u98kaVTf.js → kotlin-Dzz8TWAt.js} +0 -0
- /package/client/dist/assets/{less-CjYwpgg5.js → less-DHRJD3TR.js} +0 -0
- /package/client/dist/assets/{lexon-YTjaAFBB.js → lexon-5Y3QgTmT.js} +0 -0
- /package/client/dist/assets/{lua-BzmkWv27.js → lua-sKvhfPn5.js} +0 -0
- /package/client/dist/assets/{m3-CFwk9fw0.js → m3-DWDVwkFG.js} +0 -0
- /package/client/dist/assets/{markdown-CR5iMpSZ.js → markdown-CD_aSBxW.js} +0 -0
- /package/client/dist/assets/{mips-CcEalc17.js → mips-687T03hg.js} +0 -0
- /package/client/dist/assets/{msdax-BQbkawnr.js → msdax-C1St-dIV.js} +0 -0
- /package/client/dist/assets/{mysql-GTlaaW_P.js → mysql-BG7r8oBS.js} +0 -0
- /package/client/dist/assets/{nav-C2YXcbZS.js → nav-B05EYB0b.js} +0 -0
- /package/client/dist/assets/{nav-D2bOGSEg.js → nav-BNGCq-0y.js} +0 -0
- /package/client/dist/assets/{nav-BEL3MTwK.js → nav-BRInPX8a.js} +0 -0
- /package/client/dist/assets/{nav-CtYwmMgu.js → nav-Bf87DRHD.js} +0 -0
- /package/client/dist/assets/{nav-iH1V5j6o.js → nav-BkVzzFpc.js} +0 -0
- /package/client/dist/assets/{nav-0fwkrgHt.js → nav-BzFLtS1W.js} +0 -0
- /package/client/dist/assets/{nav-ClzOE4mA.js → nav-DBDbQOYn.js} +0 -0
- /package/client/dist/assets/{nav-B_G-TJDW.js → nav-X9sVtUWC.js} +0 -0
- /package/client/dist/assets/{objective-c-Byu1T5if.js → objective-c-Ds1-m05L.js} +0 -0
- /package/client/dist/assets/{pascal-BrfzBfRm.js → pascal-BKK9FpIi.js} +0 -0
- /package/client/dist/assets/{pascaligo-BXXKFUeo.js → pascaligo-SRS3nwtO.js} +0 -0
- /package/client/dist/assets/{perl-B3OikKq-.js → perl-B2hTOlrF.js} +0 -0
- /package/client/dist/assets/{pgsql-CTsa0Acc.js → pgsql-DIQJYNpL.js} +0 -0
- /package/client/dist/assets/{php-DiQh3FUW.js → php-BEaVe8X2.js} +0 -0
- /package/client/dist/assets/{pla-92uH8Fzm.js → pla-oPLHpZ-Q.js} +0 -0
- /package/client/dist/assets/{postiats-BbeWkKUr.js → postiats-D_vzrAzD.js} +0 -0
- /package/client/dist/assets/{powerquery-DgDMzpsm.js → powerquery-BKG6w-FH.js} +0 -0
- /package/client/dist/assets/{powershell-BfdUUzaG.js → powershell-B3dLhDt4.js} +0 -0
- /package/client/dist/assets/{protobuf-BojW2ftW.js → protobuf-DC8SGjcl.js} +0 -0
- /package/client/dist/assets/{pug-BxqTg3IU.js → pug-D5E-4fI0.js} +0 -0
- /package/client/dist/assets/{qsharp-BX_A-MW9.js → qsharp-6vJAWv0x.js} +0 -0
- /package/client/dist/assets/{r-D9BMnxvJ.js → r-CDwsEcbM.js} +0 -0
- /package/client/dist/assets/{redis-5cJqEQJJ.js → redis-CuQbbESS.js} +0 -0
- /package/client/dist/assets/{redshift-d8BBqiwb.js → redshift-B9e1k-qI.js} +0 -0
- /package/client/dist/assets/{restructuredtext-C8a6yIcZ.js → restructuredtext-BiJ5IwaU.js} +0 -0
- /package/client/dist/assets/{ruby-egeh-6KX.js → ruby-B0UAHY9b.js} +0 -0
- /package/client/dist/assets/{rust-a3r9IInB.js → rust-Dg_spmFr.js} +0 -0
- /package/client/dist/assets/{sb-y8iRIDei.js → sb-DjU66I8Q.js} +0 -0
- /package/client/dist/assets/{scala-BPDK2AmK.js → scala-qvStIdfG.js} +0 -0
- /package/client/dist/assets/{scheme-BIWUEoOs.js → scheme-FstEk5Rh.js} +0 -0
- /package/client/dist/assets/{scss-CA-PSzwg.js → scss-w0U3rQLK.js} +0 -0
- /package/client/dist/assets/{settings-CTcwN9RE.js → settings-5tzo0Rn3.js} +0 -0
- /package/client/dist/assets/{settings-D_dujJZI.js → settings-BDAW3trC.js} +0 -0
- /package/client/dist/assets/{settings-Bg0A3zoS.js → settings-BEWv3VEu.js} +0 -0
- /package/client/dist/assets/{settings-BgPqg2nv.js → settings-BORg56um.js} +0 -0
- /package/client/dist/assets/{settings-BSze3_9q.js → settings-D3LurcR5.js} +0 -0
- /package/client/dist/assets/{settings-CSJ0ahZ8.js → settings-DcqWIEM6.js} +0 -0
- /package/client/dist/assets/{settings-DYIV89nV.js → settings-Dfz8QbZS.js} +0 -0
- /package/client/dist/assets/{settings-DDcfx_ca.js → settings-yMubjqYw.js} +0 -0
- /package/client/dist/assets/{shell--LiT1Bja.js → shell-DJ78wREd.js} +0 -0
- /package/client/dist/assets/{solidity-DdqZccZg.js → solidity-1aGIVsdX.js} +0 -0
- /package/client/dist/assets/{sophia-S6-YxNG_.js → sophia-40LqcGjB.js} +0 -0
- /package/client/dist/assets/{sparql-BSf5kMp2.js → sparql-Cz5dqG_g.js} +0 -0
- /package/client/dist/assets/{sql-D7KgjR8G.js → sql-64f62Ni4.js} +0 -0
- /package/client/dist/assets/{st-BnoDa-Ml.js → st-gJe2yG8J.js} +0 -0
- /package/client/dist/assets/{swift-DEUHTkUX.js → swift-C6ME22mv.js} +0 -0
- /package/client/dist/assets/{systemverilog-Tqb_KPnW.js → systemverilog-CEWz259w.js} +0 -0
- /package/client/dist/assets/{tcl-BmBFS2qq.js → tcl-CcLVIi3m.js} +0 -0
- /package/client/dist/assets/{terminal-Bje4ziIa.js → terminal-BYtreaaF.js} +0 -0
- /package/client/dist/assets/{terminal-CSONJOex.js → terminal-C0xx0SjA.js} +0 -0
- /package/client/dist/assets/{terminal-DeWzh6ys.js → terminal-CPpK58RC.js} +0 -0
- /package/client/dist/assets/{terminal-C2WYcFHF.js → terminal-CdxkpafL.js} +0 -0
- /package/client/dist/assets/{terminal-DEqzGtcr.js → terminal-Ciia0wh2.js} +0 -0
- /package/client/dist/assets/{terminal-80yDMgMF.js → terminal-DHIkiWcs.js} +0 -0
- /package/client/dist/assets/{terminal-lkZYR4wJ.js → terminal-DY42QANg.js} +0 -0
- /package/client/dist/assets/{terminal-YOlsJCQj.js → terminal-DoxtVdma.js} +0 -0
- /package/client/dist/assets/{tickets-DYvafSaY.js → tickets-0rM0lIXd.js} +0 -0
- /package/client/dist/assets/{tickets-DNOANUXr.js → tickets-1UIGf_oA.js} +0 -0
- /package/client/dist/assets/{tickets-DlpC_iTg.js → tickets-9kdPXInd.js} +0 -0
- /package/client/dist/assets/{tickets-CF2PYelu.js → tickets-C6pwZwt4.js} +0 -0
- /package/client/dist/assets/{tickets-CB7N30gm.js → tickets-DAjtxAVb.js} +0 -0
- /package/client/dist/assets/{tickets-DU1aqsbr.js → tickets-DNmXcAwu.js} +0 -0
- /package/client/dist/assets/{tickets-clefmXLv.js → tickets-n23kDqJT.js} +0 -0
- /package/client/dist/assets/{tickets-DucYgtdl.js → tickets-tGx5AR5b.js} +0 -0
- /package/client/dist/assets/{twig-BQV8igWC.js → twig-DvsO-WjW.js} +0 -0
- /package/client/dist/assets/{typespec-DlFroUGY.js → typespec-Brkt3IAA.js} +0 -0
- /package/client/dist/assets/{vb-BlrJpIMX.js → vb-r121Uzxt.js} +0 -0
- /package/client/dist/assets/{wgsl-BWgIc6FZ.js → wgsl-BRX8uYh4.js} +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared types for the per-project Jira integration.
|
|
3
|
+
//
|
|
4
|
+
// Design: Desktop is the sync layer. The local `.specrails/local-tickets.json`
|
|
5
|
+
// store stays the canonical read cache (specrails-core reads it unchanged). Jira
|
|
6
|
+
// is the system of record. Every write to Jira goes through a durable
|
|
7
|
+
// transactional outbox in the per-project `jobs.sqlite`. See
|
|
8
|
+
// docs/jira-integration-plan.md.
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createJiraRouter = createJiraRouter;
|
|
37
|
+
const express_1 = require("express");
|
|
38
|
+
const jira_db_1 = require("./jira/jira-db");
|
|
39
|
+
const feature_flags_1 = require("./feature-flags");
|
|
40
|
+
// req.projectCtx is declared in project-router / rails-router.
|
|
41
|
+
/**
|
|
42
|
+
* Per-project Jira router, mounted at /api/projects/:projectId/jira. Gated by
|
|
43
|
+
* SPECRAILS_JIRA_SECTION; 404s entirely when the flag is off. Endpoints back the
|
|
44
|
+
* step-by-step setup wizard (test → pick project → optional status map → connect)
|
|
45
|
+
* and the sync/outbox management surfaces. The token is never returned to the
|
|
46
|
+
* client (connection responses carry `hasToken` only).
|
|
47
|
+
*/
|
|
48
|
+
function createJiraRouter() {
|
|
49
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
50
|
+
function ctx(req) {
|
|
51
|
+
return req.projectCtx;
|
|
52
|
+
}
|
|
53
|
+
// Feature-flag gate for every route under /jira.
|
|
54
|
+
router.use((_req, res, next) => {
|
|
55
|
+
if (!(0, feature_flags_1.isJiraEnabled)()) {
|
|
56
|
+
res.status(404).json({ error: 'Jira integration disabled' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
next();
|
|
60
|
+
});
|
|
61
|
+
// ─── Connection ──────────────────────────────────────────────────────────────
|
|
62
|
+
// GET /connection — current connection (token redacted) or { connected: false }.
|
|
63
|
+
router.get('/connection', (req, res) => {
|
|
64
|
+
const c = ctx(req);
|
|
65
|
+
const conn = (0, jira_db_1.getConnectionPublic)(c.db, c.project.id);
|
|
66
|
+
if (!conn) {
|
|
67
|
+
res.json({ connected: false });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
res.json({ connected: true, connection: conn, outbox: c.jiraSyncManager.outboxCounts() });
|
|
71
|
+
});
|
|
72
|
+
// POST /test — wizard step 1: validate credentials without saving.
|
|
73
|
+
router.post('/test', async (req, res) => {
|
|
74
|
+
const { baseUrl, accountEmail, token } = req.body ?? {};
|
|
75
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token)) {
|
|
76
|
+
res.status(400).json({ error: 'baseUrl and token are required' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const result = await ctx(req).jiraSyncManager.probeCredentials({
|
|
80
|
+
baseUrl: baseUrl.trim(),
|
|
81
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
82
|
+
token,
|
|
83
|
+
});
|
|
84
|
+
if (!result.ok) {
|
|
85
|
+
res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
res.json(result);
|
|
89
|
+
});
|
|
90
|
+
// POST /discover-projects — wizard step 2: list visible projects.
|
|
91
|
+
router.post('/discover-projects', async (req, res) => {
|
|
92
|
+
const { baseUrl, accountEmail, token, query } = req.body ?? {};
|
|
93
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token)) {
|
|
94
|
+
res.status(400).json({ error: 'baseUrl and token are required' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const result = await ctx(req).jiraSyncManager.discoverProjects({
|
|
98
|
+
baseUrl: baseUrl.trim(),
|
|
99
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
100
|
+
token,
|
|
101
|
+
query: isNonEmptyString(query) ? query.trim() : undefined,
|
|
102
|
+
});
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
res.status(400).json({ error: result.error });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
res.json({ projects: result.projects });
|
|
108
|
+
});
|
|
109
|
+
// POST /discover-statuses — wizard step 3 (optional): the project's statuses.
|
|
110
|
+
router.post('/discover-statuses', async (req, res) => {
|
|
111
|
+
const { baseUrl, accountEmail, token, projectKey } = req.body ?? {};
|
|
112
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token) || !isNonEmptyString(projectKey)) {
|
|
113
|
+
res.status(400).json({ error: 'baseUrl, token and projectKey are required' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const result = await ctx(req).jiraSyncManager.discoverStatuses({
|
|
117
|
+
baseUrl: baseUrl.trim(),
|
|
118
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
119
|
+
token,
|
|
120
|
+
projectKey: projectKey.trim(),
|
|
121
|
+
});
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
res.status(400).json({ error: result.error });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
res.json({ statuses: result.statuses });
|
|
127
|
+
});
|
|
128
|
+
// POST /connect — wizard final step: validate + persist + start sync.
|
|
129
|
+
router.post('/connect', async (req, res) => {
|
|
130
|
+
const c = ctx(req);
|
|
131
|
+
const { baseUrl, accountEmail, token, jiraProjectKey, statusMap, discardStatus } = req.body ?? {};
|
|
132
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token) || !isNonEmptyString(jiraProjectKey)) {
|
|
133
|
+
res.status(400).json({ error: 'baseUrl, token and jiraProjectKey are required' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const cleanMap = sanitizeStatusMap(statusMap);
|
|
137
|
+
const result = await c.jiraSyncManager.connect({
|
|
138
|
+
baseUrl: baseUrl.trim(),
|
|
139
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
140
|
+
token,
|
|
141
|
+
jiraProjectKey: jiraProjectKey.trim(),
|
|
142
|
+
statusMap: cleanMap,
|
|
143
|
+
discardStatus: isNonEmptyString(discardStatus) ? discardStatus.trim() : null,
|
|
144
|
+
});
|
|
145
|
+
if (!result.ok) {
|
|
146
|
+
res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
res.status(201).json({ connection: (0, jira_db_1.getConnectionPublic)(c.db, c.project.id) });
|
|
150
|
+
});
|
|
151
|
+
// PATCH /connection — toggle enabled (hot-swap local↔Jira) and/or update the
|
|
152
|
+
// discard "move-to" status. `discardStatus: ''`/null clears it.
|
|
153
|
+
router.patch('/connection', (req, res) => {
|
|
154
|
+
const c = ctx(req);
|
|
155
|
+
const existing = (0, jira_db_1.getConnectionPublic)(c.db, c.project.id);
|
|
156
|
+
if (!existing) {
|
|
157
|
+
res.status(404).json({ error: 'No Jira connection configured' });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const { enabled, discardStatus, statusMap } = req.body ?? {};
|
|
161
|
+
if (typeof enabled === 'boolean') {
|
|
162
|
+
c.jiraSyncManager.setEnabled(enabled);
|
|
163
|
+
}
|
|
164
|
+
if (discardStatus !== undefined) {
|
|
165
|
+
c.jiraSyncManager.setDiscardStatus(isNonEmptyString(discardStatus) ? discardStatus.trim() : null);
|
|
166
|
+
}
|
|
167
|
+
if (statusMap !== undefined) {
|
|
168
|
+
c.jiraSyncManager.setStatusMap(sanitizeStatusMap(statusMap));
|
|
169
|
+
}
|
|
170
|
+
res.json({ connection: (0, jira_db_1.getConnectionPublic)(c.db, c.project.id) });
|
|
171
|
+
});
|
|
172
|
+
// GET /statuses — the connected project's real statuses (post-connect picker
|
|
173
|
+
// for the discard "move-to" status). Uses the stored credentials.
|
|
174
|
+
router.get('/statuses', async (req, res) => {
|
|
175
|
+
const result = await ctx(req).jiraSyncManager.listStatusesForConnection();
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
res.status(400).json({ error: result.error });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
res.json({ statuses: result.statuses });
|
|
181
|
+
});
|
|
182
|
+
// DELETE /connection — remove the connection + restore local backlog config.
|
|
183
|
+
router.delete('/connection', (req, res) => {
|
|
184
|
+
const c = ctx(req);
|
|
185
|
+
c.jiraSyncManager.disconnect();
|
|
186
|
+
res.json({ connected: false });
|
|
187
|
+
});
|
|
188
|
+
// POST /resume — re-paste of a fresh token after a 401: drain the parked outbox.
|
|
189
|
+
router.post('/resume', (req, res) => {
|
|
190
|
+
ctx(req).jiraSyncManager.resumeAfterReauth();
|
|
191
|
+
res.json({ ok: true });
|
|
192
|
+
});
|
|
193
|
+
// ─── Sync + outbox management ───────────────────────────────────────────────
|
|
194
|
+
// POST /sync — manual "Sync now": a FULL re-fetch (ignores the high-water) so
|
|
195
|
+
// it back-fills any data the cache is missing (e.g. sprint/epic fields).
|
|
196
|
+
router.post('/sync', async (req, res) => {
|
|
197
|
+
const result = await ctx(req).jiraSyncManager.pollOnce(true);
|
|
198
|
+
res.json({ ok: true, upserted: result?.upserted ?? 0 });
|
|
199
|
+
});
|
|
200
|
+
// GET /outbox?state= — list outbox ops (defaults to all).
|
|
201
|
+
router.get('/outbox', (req, res) => {
|
|
202
|
+
const c = ctx(req);
|
|
203
|
+
const state = req.query.state;
|
|
204
|
+
const valid = ['pending', 'inflight', 'done', 'dead'];
|
|
205
|
+
const filter = valid.includes(state) ? state : undefined;
|
|
206
|
+
res.json({ ops: c.jiraSyncManager.listOutbox(filter), counts: c.jiraSyncManager.outboxCounts() });
|
|
207
|
+
});
|
|
208
|
+
// POST /outbox/:id/retry — re-queue a dead-lettered op for a manual retry.
|
|
209
|
+
router.post('/outbox/:id/retry', async (req, res) => {
|
|
210
|
+
const c = ctx(req);
|
|
211
|
+
const id = parseInt(req.params.id, 10);
|
|
212
|
+
if (Number.isNaN(id)) {
|
|
213
|
+
res.status(400).json({ error: 'Invalid op id' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const { retryDeadOutbox } = await Promise.resolve().then(() => __importStar(require('./jira/jira-db')));
|
|
217
|
+
const ok = retryDeadOutbox(c.db, id);
|
|
218
|
+
if (!ok) {
|
|
219
|
+
res.status(404).json({ error: 'Op not found or not in dead state' });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
void c.jiraSyncManager.drainOnce().catch(() => undefined);
|
|
223
|
+
res.json({ ok: true });
|
|
224
|
+
});
|
|
225
|
+
// POST /specs — Add Spec when the project source is Jira: create the issue in
|
|
226
|
+
// Jira and materialize it locally. Keeps the generic local ticket-create path
|
|
227
|
+
// untouched.
|
|
228
|
+
router.post('/specs', async (req, res) => {
|
|
229
|
+
const c = ctx(req);
|
|
230
|
+
const { title, description, labels, priority, issueType } = req.body ?? {};
|
|
231
|
+
if (!isNonEmptyString(title)) {
|
|
232
|
+
res.status(400).json({ error: 'title is required' });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const result = await c.jiraSyncManager.createSpec({
|
|
236
|
+
title: title.trim(),
|
|
237
|
+
description: isNonEmptyString(description) ? description : undefined,
|
|
238
|
+
labels: Array.isArray(labels) ? labels.filter((l) => typeof l === 'string') : undefined,
|
|
239
|
+
priority: isNonEmptyString(priority) ? priority : undefined,
|
|
240
|
+
issueType: isNonEmptyString(issueType) ? issueType : undefined,
|
|
241
|
+
});
|
|
242
|
+
if (!result.ok) {
|
|
243
|
+
res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
res.status(201).json({ localId: result.localId, jiraKey: result.jiraKey });
|
|
247
|
+
});
|
|
248
|
+
// POST /specs/:localId/discard — "Move to <discard status>": transition the
|
|
249
|
+
// linked issue to the configured discard status (+ optional reason comment)
|
|
250
|
+
// instead of a destructive delete. Body: { comment?: string }.
|
|
251
|
+
router.post('/specs/:localId/discard', (req, res) => {
|
|
252
|
+
const c = ctx(req);
|
|
253
|
+
const localId = parseInt(req.params.localId, 10);
|
|
254
|
+
if (Number.isNaN(localId)) {
|
|
255
|
+
res.status(400).json({ error: 'Invalid spec id' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const comment = isNonEmptyString(req.body?.comment) ? req.body.comment : null;
|
|
259
|
+
const result = c.jiraSyncManager.discardSpec(localId, comment);
|
|
260
|
+
if (!result.ok) {
|
|
261
|
+
const status = result.reason === 'no-link' ? 404 : 409;
|
|
262
|
+
res.status(status).json({ error: result.reason });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
res.status(202).json({ ok: true });
|
|
266
|
+
});
|
|
267
|
+
// GET /specs/:localId/details — read-only "Jira details" + "Development" payload
|
|
268
|
+
// for the spec detail modal. Resilient: dev-status failures still return fields.
|
|
269
|
+
router.get('/specs/:localId/details', async (req, res) => {
|
|
270
|
+
const c = ctx(req);
|
|
271
|
+
const localId = parseInt(req.params.localId, 10);
|
|
272
|
+
if (Number.isNaN(localId)) {
|
|
273
|
+
res.status(400).json({ error: 'Invalid spec id' });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const result = await c.jiraSyncManager.getSpecDetails(localId);
|
|
277
|
+
if (!result.ok) {
|
|
278
|
+
const status = result.reason === 'no-link' ? 404 : result.reason === 'not-active' ? 409 : (result.status ?? 502);
|
|
279
|
+
res.status(status).json({ error: result.reason });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
res.json(result.details);
|
|
283
|
+
});
|
|
284
|
+
// GET /links — the spec↔issue map (for the badge / diagnostics).
|
|
285
|
+
router.get('/links', (req, res) => {
|
|
286
|
+
res.json({ links: ctx(req).jiraSyncManager.listLinks() });
|
|
287
|
+
});
|
|
288
|
+
return router;
|
|
289
|
+
}
|
|
290
|
+
function isNonEmptyString(v) {
|
|
291
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
292
|
+
}
|
|
293
|
+
function sanitizeStatusMap(raw) {
|
|
294
|
+
if (!raw || typeof raw !== 'object')
|
|
295
|
+
return null;
|
|
296
|
+
const states = ['todo', 'in_progress', 'done', 'cancelled'];
|
|
297
|
+
const out = {};
|
|
298
|
+
for (const s of states) {
|
|
299
|
+
const v = raw[s];
|
|
300
|
+
if (typeof v === 'string' && v.trim())
|
|
301
|
+
out[s] = v.trim();
|
|
302
|
+
}
|
|
303
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
304
|
+
}
|
|
@@ -24,6 +24,7 @@ const terminal_manager_1 = require("./terminal-manager");
|
|
|
24
24
|
const browser_capture_manager_1 = require("./browser-capture-manager");
|
|
25
25
|
const explore_cwd_manager_1 = require("./explore-cwd-manager");
|
|
26
26
|
const ticket_store_1 = require("./ticket-store");
|
|
27
|
+
const jira_sync_manager_1 = require("./jira/jira-sync-manager");
|
|
27
28
|
const rails_store_1 = require("./rails-store");
|
|
28
29
|
const desktop_db_1 = require("./desktop-db");
|
|
29
30
|
const config_1 = require("./config");
|
|
@@ -115,6 +116,11 @@ class ProjectRegistry {
|
|
|
115
116
|
catch { /* ignore */ }
|
|
116
117
|
// Tear down the embedded browser (closes pages + persistent context).
|
|
117
118
|
void ctx.browserCaptureManager.shutdown().catch(() => { });
|
|
119
|
+
// Stop the Jira sync poll/drain timers (no children, just intervals).
|
|
120
|
+
try {
|
|
121
|
+
ctx.jiraSyncManager.stop();
|
|
122
|
+
}
|
|
123
|
+
catch { /* ignore */ }
|
|
118
124
|
// Kill any terminal sessions belonging to this project
|
|
119
125
|
try {
|
|
120
126
|
(0, terminal_manager_1.getTerminalManager)().killAllForProject(id);
|
|
@@ -201,6 +207,10 @@ class ProjectRegistry {
|
|
|
201
207
|
}
|
|
202
208
|
catch { /* ignore */ }
|
|
203
209
|
void ctx.browserCaptureManager.shutdown().catch(() => { });
|
|
210
|
+
try {
|
|
211
|
+
ctx.jiraSyncManager.stop();
|
|
212
|
+
}
|
|
213
|
+
catch { /* ignore */ }
|
|
204
214
|
// Release chokidar watchers + abort in-flight generations so a restart
|
|
205
215
|
// does not leak handles/children — mirror removeProject()'s per-project teardown.
|
|
206
216
|
try {
|
|
@@ -250,6 +260,14 @@ class ProjectRegistry {
|
|
|
250
260
|
catch { /* queue_state table may not exist yet */ }
|
|
251
261
|
const webhookManager = this._webhookManager;
|
|
252
262
|
const railJobs = new Map();
|
|
263
|
+
// Jira sync (per-project, inert until a connection is configured). Constructed
|
|
264
|
+
// before QueueManager so the onJobFinished closure can reference it.
|
|
265
|
+
const jiraSyncManager = new jira_sync_manager_1.JiraSyncManager({
|
|
266
|
+
db,
|
|
267
|
+
projectId: project.id,
|
|
268
|
+
projectPath: project.path,
|
|
269
|
+
broadcast: boundBroadcast,
|
|
270
|
+
});
|
|
253
271
|
const queueManager = new queue_manager_1.QueueManager(boundBroadcast, db, undefined, project.path, {
|
|
254
272
|
zombieTimeoutMs: projectZombieTimeout,
|
|
255
273
|
provider: project.provider ?? 'claude',
|
|
@@ -318,6 +336,26 @@ class ProjectRegistry {
|
|
|
318
336
|
timestamp: ticket.updated_at,
|
|
319
337
|
});
|
|
320
338
|
}
|
|
339
|
+
// Jira write-back (inert for non-Jira projects): enqueue the status
|
|
340
|
+
// transition + completion comment per linked ticket. The LOCAL mutation
|
|
341
|
+
// above stays synchronous; only the durable outbox enqueue happens here,
|
|
342
|
+
// wrapped so a Jira failure can never break the job-exit handler.
|
|
343
|
+
if (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated') {
|
|
344
|
+
try {
|
|
345
|
+
const needsReviewIds = completedTicketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
|
|
346
|
+
jiraSyncManager.onJobOutcome({
|
|
347
|
+
ticketIds: completedTicketIds,
|
|
348
|
+
status,
|
|
349
|
+
jobId,
|
|
350
|
+
costUsd: costUsd ?? null,
|
|
351
|
+
durationMs: jobRow?.duration_ms ?? null,
|
|
352
|
+
needsReviewIds,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
console.error('[project-registry] jira onJobOutcome failed:', err);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
321
359
|
}
|
|
322
360
|
catch (err) {
|
|
323
361
|
console.error('[project-registry] failed to apply job outcome to tickets:', err);
|
|
@@ -425,6 +463,10 @@ class ProjectRegistry {
|
|
|
425
463
|
}
|
|
426
464
|
const ticketWatcher = new ticket_watcher_1.TicketWatcher(project.path, project.id, boundBroadcast);
|
|
427
465
|
ticketWatcher.start();
|
|
466
|
+
// Suppress the file-watcher echo for the Jira sync's own writes (the every-60s
|
|
467
|
+
// poll would otherwise trigger a full-board refresh = flicker). Late-bound
|
|
468
|
+
// because the JiraSyncManager is constructed before the watcher.
|
|
469
|
+
jiraSyncManager.setLocalWriteNotifier((rev) => ticketWatcher.notifyDesktopWrite(rev));
|
|
428
470
|
// BrowserCaptureManager — "Add Spec from browser". Constructed for every
|
|
429
471
|
// project regardless of the feature flag; the routes + WS endpoint 404 when
|
|
430
472
|
// the flag is off, and the persistent Chromium context is launched lazily on
|
|
@@ -435,7 +477,7 @@ class ProjectRegistry {
|
|
|
435
477
|
db,
|
|
436
478
|
broadcast: boundBroadcast,
|
|
437
479
|
});
|
|
438
|
-
const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, broadcast: boundBroadcast, railJobs };
|
|
480
|
+
const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs };
|
|
439
481
|
this._contexts.set(project.id, ctx);
|
|
440
482
|
return ctx;
|
|
441
483
|
}
|
|
@@ -61,6 +61,27 @@ const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
|
61
61
|
const multer_1 = __importDefault(require("multer"));
|
|
62
62
|
const attachment_manager_1 = require("./attachment-manager");
|
|
63
63
|
const project_router_helpers_1 = require("./project-router-helpers");
|
|
64
|
+
/**
|
|
65
|
+
* Add Spec on a Jira-backed project: promote the freshly-created LOCAL ticket to
|
|
66
|
+
* a Jira issue (best-effort, server-side). The per-spec `createLocal` escape
|
|
67
|
+
* hatch keeps it local. On any failure the ticket simply stays local and a
|
|
68
|
+
* non-fatal warning is broadcast — a spec is never lost to a Jira error.
|
|
69
|
+
*/
|
|
70
|
+
async function maybePromoteSpecToJira(c, ticketId, createLocal, broadcast) {
|
|
71
|
+
// jiraSyncManager is always present in production (constructed per project);
|
|
72
|
+
// guard defensively so partial test contexts and a disabled feature are no-ops.
|
|
73
|
+
if (createLocal || !c.jiraSyncManager?.isActive())
|
|
74
|
+
return;
|
|
75
|
+
try {
|
|
76
|
+
const r = await c.jiraSyncManager.promoteTicketToJira(ticketId);
|
|
77
|
+
if (!r.ok) {
|
|
78
|
+
broadcast({ type: 'jira.sync_error', projectId: c.project.id, reason: `Kept as a local spec — couldn't create it in Jira: ${r.error}` });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error('[project-router] jira promote failed:', err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
64
85
|
function registerTicketsRoutes(deps) {
|
|
65
86
|
const { router, registry, ctx, ticketPath } = deps;
|
|
66
87
|
// ─── Tickets ──────────────────────────────────────────────────────────────────
|
|
@@ -490,6 +511,9 @@ function registerTicketsRoutes(deps) {
|
|
|
490
511
|
ticket: created, timestamp: new Date().toISOString(),
|
|
491
512
|
};
|
|
492
513
|
broadcast(doneMsg);
|
|
514
|
+
// Add Spec → Jira: promote the new ticket to a Jira issue when the
|
|
515
|
+
// project is Jira-backed (unless the user opted to keep it local).
|
|
516
|
+
void maybePromoteSpecToJira(ctx(req), created.id, req.body?.createLocal === true, broadcast);
|
|
493
517
|
// Quick mode Contract Refine: when toggle is on in the request body
|
|
494
518
|
// AND the project setting + kill switch permit it, fire the no-resume
|
|
495
519
|
// Quick refine path asynchronously. Claude-only today — codex
|
|
@@ -887,6 +911,9 @@ function registerTicketsRoutes(deps) {
|
|
|
887
911
|
}
|
|
888
912
|
}
|
|
889
913
|
res.status(201).json({ ticket: created, revision: store.revision });
|
|
914
|
+
// Add Spec (Explore) → Jira: promote the committed ticket to a Jira issue
|
|
915
|
+
// when the project is Jira-backed (unless the user opted to keep it local).
|
|
916
|
+
void maybePromoteSpecToJira(ctx(req), created.id, body.createLocal === true, broadcast);
|
|
890
917
|
// Fire Contract Refine post-commit (fire-and-forget). Toggle + kill-switch
|
|
891
918
|
// are checked inside runContractRefine. Claude-only today — codex
|
|
892
919
|
// contract refine isn't wired (the spawn hardcodes the `claude`
|
|
@@ -1329,10 +1356,31 @@ function registerTicketsRoutes(deps) {
|
|
|
1329
1356
|
res.status(404).json({ error: 'Ticket not found' });
|
|
1330
1357
|
return;
|
|
1331
1358
|
}
|
|
1332
|
-
const { broadcast, ticketWatcher } = ctx(req);
|
|
1359
|
+
const { broadcast, ticketWatcher, jiraSyncManager } = ctx(req);
|
|
1333
1360
|
ticketWatcher.notifyDesktopWrite(store.revision);
|
|
1334
1361
|
const msg = { type: 'ticket_updated', ticket: updated, projectId: ctx(req).project.id, timestamp: new Date().toISOString() };
|
|
1335
1362
|
broadcast(msg);
|
|
1363
|
+
// Write the edited fields back to Jira for Jira-backed specs (no-op
|
|
1364
|
+
// otherwise). Uses the FINAL stored values (e.g. acceptance-criteria
|
|
1365
|
+
// folding) for the fields that were actually changed. Never breaks the
|
|
1366
|
+
// local save — a Jira hiccup only fails the enqueue, which is caught.
|
|
1367
|
+
try {
|
|
1368
|
+
const u = updated;
|
|
1369
|
+
const changes = {};
|
|
1370
|
+
if (title !== undefined)
|
|
1371
|
+
changes.title = u.title;
|
|
1372
|
+
if (description !== undefined || acceptanceCriteria !== undefined)
|
|
1373
|
+
changes.description = u.description;
|
|
1374
|
+
if (priority !== undefined)
|
|
1375
|
+
changes.priority = u.priority ?? null;
|
|
1376
|
+
if (labels !== undefined)
|
|
1377
|
+
changes.labels = u.labels;
|
|
1378
|
+
if (Object.keys(changes).length > 0)
|
|
1379
|
+
jiraSyncManager?.onSpecEdited(Number(ticketId), changes);
|
|
1380
|
+
}
|
|
1381
|
+
catch (e) {
|
|
1382
|
+
console.error('[project-router] jira write-back enqueue failed:', e);
|
|
1383
|
+
}
|
|
1336
1384
|
res.json({ ticket: updated, revision: store.revision });
|
|
1337
1385
|
}
|
|
1338
1386
|
catch (err) {
|
|
@@ -8,6 +8,7 @@ const rails_router_1 = require("./rails-router");
|
|
|
8
8
|
const profiles_router_1 = require("./profiles-router");
|
|
9
9
|
const plugins_router_1 = require("./plugins-router");
|
|
10
10
|
const code_explorer_router_1 = require("./code-explorer-router");
|
|
11
|
+
const jira_router_1 = require("./jira-router");
|
|
11
12
|
const ticket_store_1 = require("./ticket-store");
|
|
12
13
|
const project_router_jobs_1 = require("./project-router-jobs");
|
|
13
14
|
const project_router_spending_1 = require("./project-router-spending");
|
|
@@ -74,6 +75,9 @@ function createProjectRouter(registry) {
|
|
|
74
75
|
// Mount plugins router under each project (per-project marketplace)
|
|
75
76
|
const pluginsRouter = (0, plugins_router_1.createPluginsRouter)();
|
|
76
77
|
router.use('/:projectId/plugins', pluginsRouter);
|
|
78
|
+
// Mount Jira router under each project (per-project Jira board sync)
|
|
79
|
+
const jiraRouter = (0, jira_router_1.createJiraRouter)();
|
|
80
|
+
router.use('/:projectId/jira', jiraRouter);
|
|
77
81
|
// Mount Code-Explorer router. FileSummaryManager comes from ProjectContext.
|
|
78
82
|
const codeRouterByCtx = new WeakMap();
|
|
79
83
|
router.use('/:projectId/code', (req, res, next) => {
|
|
@@ -270,6 +270,12 @@ function createRailsRouter() {
|
|
|
270
270
|
});
|
|
271
271
|
jobIds.push(job.id);
|
|
272
272
|
c.railJobs.set(job.id, { railIndex, mode, ticketIds: [ticketId] });
|
|
273
|
+
// Jira write-back: push In Progress for any Jira-linked ticket (inert
|
|
274
|
+
// for non-Jira projects). Best-effort — never blocks the launch.
|
|
275
|
+
try {
|
|
276
|
+
c.jiraSyncManager.onRailLaunch([ticketId], job.id);
|
|
277
|
+
}
|
|
278
|
+
catch { /* non-fatal */ }
|
|
273
279
|
}
|
|
274
280
|
jobId = jobIds[0];
|
|
275
281
|
const startMsg = {
|
|
@@ -291,6 +297,12 @@ function createRailsRouter() {
|
|
|
291
297
|
const job = c.queueManager.enqueue(command, 'normal', { profileName: resolvedProfile, provider: railProvider });
|
|
292
298
|
jobId = job.id;
|
|
293
299
|
c.railJobs.set(jobId, { railIndex, mode, ticketIds: [...rail.ticketIds] });
|
|
300
|
+
// Jira write-back: push In Progress for any Jira-linked ticket (inert for
|
|
301
|
+
// non-Jira projects). Best-effort — never blocks the launch.
|
|
302
|
+
try {
|
|
303
|
+
c.jiraSyncManager.onRailLaunch([...rail.ticketIds], jobId);
|
|
304
|
+
}
|
|
305
|
+
catch { /* non-fatal */ }
|
|
294
306
|
const startMsg = {
|
|
295
307
|
type: 'rail.job_started',
|
|
296
308
|
projectId: c.project.id,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{Ct as t,Jt as n,M as r,Mt as i,Nt as a,Pt as o,nn as s,ot as c,q as l,st as u,vt as d,xt as f,yt as p}from"./index-CGHKpC-N.js";var m=p(`ban`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M4.929 4.929 19.07 19.071`,key:`196cmz`}]]),h=e(s(),1);function g(e){let t=new Set;return e.filter(e=>{let n=`${e.type}:${e.jobId}`;return t.has(n)?!1:(t.add(n),!0)})}function _({activeProjectId:e,limit:n=50}){let[r,a]=(0,h.useState)([]),[s,c]=(0,h.useState)(!1),[l,u]=(0,h.useState)(!0),d=(0,h.useRef)(e);(0,h.useEffect)(()=>{d.current=e},[e]);let{registerHandler:f,unregisterHandler:p}=i();(0,h.useEffect)(()=>{if(!e){a([]),u(!0);return}let t=!1;c(!0),a([]),u(!0);async function r(){let e=o();try{let r=await fetch(`${e}/activity?limit=${n}`);if(!r.ok||t)return;let i=await r.json();if(t)return;a(i),u(i.length===n)}catch{}finally{t||c(!1)}}return r(),()=>{t=!0}},[e,n]);let m=(0,h.useCallback)(async()=>{if(s||!l)return;c(!0);let e=o();try{a(t=>{let r=t[t.length-1];if(!r)return t;let i=encodeURIComponent(r.timestamp);return fetch(`${e}/activity?limit=${n}&before=${i}`).then(e=>e.json()).then(e=>{a(t=>g([...t,...e])),u(e.length===n),c(!1)}).catch(()=>c(!1)),t})}catch{c(!1)}},[s,l,n]),_=(0,h.useCallback)(e=>{let n=e,r=d.current;if(!(!n||typeof n.type!=`string`)&&!(n.projectId&&n.projectId!==r)){if(n.type===`queue`&&Array.isArray(n.jobs)){let e=[];for(let t of n.jobs){let n=t.status===`completed`?`job_completed`:t.status===`failed`?`job_failed`:t.status===`canceled`?`job_canceled`:`job_started`;e.push({id:`${n}:${t.id}`,type:n,jobId:t.id,jobCommand:t.command??``,timestamp:t.startedAt??new Date().toISOString(),summary:`${n.replace(`_`,` `)}: ${t.command??``}`,costUsd:null})}e.length>0&&a(t=>g([...e,...t]))}if(n.type===`phase`&&n.phase&&n.state&&n.timestamp){let e={id:`phase:${n.phase}:${n.state}:${n.timestamp}`,type:`job_started`,jobId:`phase-${n.phase}`,jobCommand:t.t(`activity:feed.phaseCommand`,{phase:n.phase,state:n.state}),timestamp:n.timestamp,summary:`Phase ${n.phase} is ${n.state}`,costUsd:null};a(t=>g([e,...t]))}}},[]);return(0,h.useLayoutEffect)(()=>(f(`activity`,_),()=>p(`activity`)),[_,f,p]),{items:r,loading:s,hasMore:l,loadMore:m}}var v=a();function y(e,t){let n=Date.now()-new Date(e).getTime(),r=Math.floor(n/1e3);if(r<60)return t(`feed.relativeTime.seconds`,{value:r});let i=Math.floor(r/60);if(i<60)return t(`feed.relativeTime.minutes`,{value:i});let a=Math.floor(i/60);return a<24?t(`feed.relativeTime.hours`,{value:a}):t(`feed.relativeTime.days`,{value:Math.floor(a/24)})}function b({type:e}){switch(e){case`job_completed`:return(0,v.jsx)(u,{className:`w-4 h-4 text-green-500 flex-shrink-0`});case`job_failed`:return(0,v.jsx)(c,{className:`w-4 h-4 text-red-500 flex-shrink-0`});case`job_canceled`:return(0,v.jsx)(m,{className:`w-4 h-4 text-muted-foreground flex-shrink-0`});default:return(0,v.jsx)(r,{className:`w-4 h-4 text-blue-500 flex-shrink-0`})}}function x(e,t){switch(e){case`job_completed`:return t(`common:status.completed`);case`job_failed`:return t(`common:status.failed`);case`job_canceled`:return t(`common:status.canceled`);default:return t(`feed.typeStarted`)}}function S(e){switch(e){case`job_completed`:return`text-green-500`;case`job_failed`:return`text-red-500`;case`job_canceled`:return`text-muted-foreground`;default:return`text-blue-500`}}function C(){let{t:e}=n(`activity`),{activeProjectId:t}=f(),{items:r,loading:i,hasMore:a,loadMore:o}=_({activeProjectId:t}),s=(0,h.useRef)(null);return(0,h.useEffect)(()=>{let e=s.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0].isIntersecting&&a&&!i&&o()},{threshold:.1});return t.observe(e),()=>t.disconnect()},[a,i,o]),(0,v.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,v.jsxs)(`div`,{className:`flex items-center gap-2 px-4 py-3 border-b border-border bg-background/50`,children:[(0,v.jsx)(d,{className:`w-4 h-4 text-muted-foreground`}),(0,v.jsx)(`h1`,{className:`text-sm font-medium`,children:e(`feed.title`)})]}),(0,v.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[i&&r.length===0?(0,v.jsx)(`div`,{className:`flex items-center justify-center h-32`,children:(0,v.jsx)(l,{className:`w-4 h-4 animate-spin text-muted-foreground`})}):r.length===0?(0,v.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-48 gap-2 text-muted-foreground`,children:[(0,v.jsx)(d,{className:`w-8 h-8 opacity-40`}),(0,v.jsx)(`p`,{className:`text-sm`,children:e(`feed.emptyTitle`)}),(0,v.jsx)(`p`,{className:`text-xs opacity-70`,children:e(`feed.emptyHint`)})]}):(0,v.jsx)(`ul`,{className:`divide-y divide-border/50`,children:r.map(t=>(0,v.jsxs)(`li`,{className:`flex items-center gap-3 px-4 py-2.5 hover:bg-accent/30 transition-colors`,children:[(0,v.jsx)(b,{type:t.type}),(0,v.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,v.jsx)(`p`,{className:`text-xs text-foreground truncate`,title:t.jobCommand,children:t.jobCommand}),(0,v.jsxs)(`div`,{className:`flex items-center gap-2 mt-0.5`,children:[(0,v.jsx)(`span`,{className:`text-xs font-medium ${S(t.type)}`,children:x(t.type,e)}),t.costUsd!=null&&(0,v.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`$`,t.costUsd.toFixed(4)]})]})]}),(0,v.jsx)(`span`,{className:`text-xs text-muted-foreground flex-shrink-0 tabular-nums`,children:y(t.timestamp,e)})]},`${t.type}:${t.jobId}`))}),(0,v.jsx)(`div`,{ref:s,className:`h-1`}),i&&r.length>0&&(0,v.jsx)(`div`,{className:`flex justify-center py-3`,children:(0,v.jsx)(l,{className:`w-4 h-4 animate-spin text-muted-foreground`})}),!a&&r.length>0&&(0,v.jsx)(`p`,{className:`text-center text-xs text-muted-foreground py-3`,children:e(`feed.allLoaded`)})]})]})}export{C as default};
|