This commit is contained in:
Nick Beckley 2026-06-01 11:39:35 +01:00
parent cb7405c0e8
commit 30845f05cd
57 changed files with 1647 additions and 52 deletions

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUUW/cKBDHv4rFs3EAY2z8Fm1a3SrXy6pZ9U73NoahQXHwCnDaKMp3P+HdU5unqi8IjfkPM7/541cS4AnJSK5Ppw/PGHIiNcl+CwkmFGWKMn7kbGRy5G2jVSc1a/8lNfG3+EJG0ktjuNFIW1RIpWUdnVpnqLWoOeOWiU6WnPA1kfGVgG8sPnuDzZK+YEx+CWQkf/tgl2/lbvCNmZfVNnGZcR9ShmBKMX/td7f0z+vD8e5wPpUwFXHjLRmJMtr0xmmqcBioVKynA+84Fb0DAeiA93iW+ZAxBpibZB9/3G+XHDCbUTSibRgVmrzVxEKGUvIECY8vp1LFhuimxOstvG3HV/KMkYyi/h/mOd2Vmf3VU5pWP9ur85ohPaZtdWDyEl9ITU5xOWHMHjc+1ynh0zS/HCE9fjyfKdv04TuaNaPdLWvIZCS8HUhNNkDzDNkvobrUuJs9hrx9zNGH5M2vcrWkJnf31WGG7Jb49G4e92fM+9+g/AnMgw9Y7W/ISBhHYdsBqRNcUTlYoNC1mirVKofc6F5wUpPdErIP67Kmah8yfo1bS2QkH2FOJendfXUdzYPPaPIaS5//qOKrQ1zsanL1Y5acNaxpGSuiNZ/WXH1G6yOajKWJY1wv+b68lwglNtFujRFDrg6QH6o/ID2QkWgYDAcNGkAZ47Bj6JhloFhvUVmFVgrbW6mlVVIzoyRMXFro+ORKhNQEi3Wqza2mYwMDJ+ngJk6lU4qCM5YCIpNa8M4y+45jdTcXHZiBSbQt1egslZoD1bJtqW076IVyfS/cT0gufuCsALb4fH535/di7YBWcSok76jUmtNBdz2d+mHSkxqwM6XmW4wB559AffImLmlxubo4pHqH7vMayr+j2tzyzQf6fZvRzWIeMVZlxuBDeSqXsb69vb39BwAA//8DAJ/SwMyCBAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUTW/cNhD9KwKvXcr8EkXq5toOunAdG9lFW/Q2Ikc2a5laUJSTwPB/L6i1EztBgV4EYUjOe/PmzTyRCA9IOnJ6OFw8Yswz2ZAc1pBgQlOmKeN7zjqmOi5r23KmLPubbEi4xK+kI61yjjuLVKJGqjxraC8HR71Hyxn3TDSq5ITbmXRPBELt8TE4rKf5D0xzmCLpyJ8h+ulzwYZQu3FafJ2mEbdxzhBdIfNxe3ZJfz+92V/fHG/NOJfHdfCkI9pZ17rBUo3GUKVZSw1vOBXtAAJwAN7i8VmIGVOEsZ79/Xd8P+WI2XWiFrJmVFjyvCEeMhTKPcy4/3ooLFaJzkt8s4bX3+6JPGIindi8inlMd+LGcPIw90sY/cn6JRtySNMBUw64qnG9q76T4KxmtdCCMbIhV+DuQsRqe15dj6VEcIYp9JJaHDxVlgO1SkrqZQOt0EPbioFsyCWmiOObrFfBpWmehly9iFy9w9mdnl1E6EcsGB9gnItO23mXIXoYp4gXX9At+Zhrn5ZyfDbFHOIyLXO1jRlvE7ycv74/W1LCmKsbyHfVbzDfkY5YMI6DBQugnRuwYTgwz0Cz1qP2Gr0SvvXKKq+VZU4r6Lny0PB+KBGyIb8WDXeLczjP39msJhnHlUP10qezMWDM72QkHWEchZcG6SC4psp4oNBIS7WWekDubCv4K8we0i1m0pHB9H3PnOuFstyBsCD9wKTXgxWibUENsmmV0LphLTjftoIrL1t0IAfJjCAbcpMmv7j8Y6vl2oCbNP2DLhepipFZ0xgHfS+04gasQcW1Mtpia9UATEnXA28M9D1n0DBnjXDAtQXV+2b4ptJFvA0Rz8N8GOHrG1xT61r+4gAMH0D9cP1Dggf8PKX7j0cb1x8v9mRDPi2xbIRqWyzyOUT6RZeJvlrGHPZ3CcGjv5o8/uwjLONSrRPayB6RNYaitj1VpmfUKFRUqsZ7Cc5IV9Q4n9w9pqo4DEIsU/Ut2fWuOk3uLmR0eUmF318rj+tddTNCHqb08G6TvCnsRwFqITkrnfH4eNxGxy3ivUGvORWKN1RZy6mxTUv71vS21wYbVxS7XvJhydUn9CGhy2vFL15cMc/u0N3/LMarC14sylnx2u64xVZp/9cSe96QB4R5SfiwbuvuiWxjxLQiny/HUdzGqzCOYUY3RT+TTvPG1K3m+oXhf94TzNbCSvX8/Pz8LwAAAP//AwCR2jkWGwYAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBbdOumXsSLtOhtRI5sIlppQVGbBsb+94DyBol7600YaGZePvPOvJIJjkg6cns6/XLGKS+kJjluIcGEpkxTxg+cdcx0rG2U1kZo9g+pSXzAr6QjRnnPvUMqUSNVgbW0l4OnIaDjjAcmWlVqwvNCulcCsQl4jh6befmEaYnzRDryV5zC/KX0htj4cV5Dk+YRd9OSYfJFzJ+7uwf6x+3+8Lh/+2vBpSQ3MZCOOFA9Wh2otaaninGk1jtLBXBurETlXBEBsYlTxjTB2Czh84/+Yc4TZt+JRsiGUeHIpSYBMhTJPSx4+HoqKjZE9yVeb+Hts3slZ0ykE/V3mG/lbvwYb/J8GvGM4wnSgunGz8cjTIHU5JTmE6YcccOyvXQcIcd5qq7N7saIUyY1eXyq9iPkYU7Hd6ye3hDs/geBM6aedESyVmpjUEkpweleMAxGe61F74GZoRfIPbLWKeO4YVw6wZxstbEmAHOe1OR5nHsYf182fk6w3gZmhBao0AmltfMMpcWhNcgCaN1r612Q1ghlg5XgALjr0XinfVH2AfxLnLDa3ZOOMI4iSIt0EFxTZQNQaKWjWks9IPfOCE5qcjdPOU7rvC7Vbsr4nDZ+pCO/wrjgG7nb5F9iRp/XVKD+rUuzfZrD6nP1wwGcNayRjJWkNZ/WXH3EEBP6jAXvIa3Xev9JEVpsSXdrSjjlag/5pfoNlpdtJtZzcOWp2vsBW4YDCww0MwF10BiUCCYop4JWjnmtoOcqQMv7oURITbAYrto8zl0QDgZHBQdFlWKaQguGSudkb4zCtmfvOFaPY8kDb5nCIKnDIVDlOFCnpKRBtmCEHowRw09IrubjrAAOeH7b1k2BDsFi0JwKxVuqnOPUutbQ3tje9dpi64vmB0wTjj+B+hB9mpd5yNXVu9U7dB/XqVycavPxlzjRf7cZ3c/+M6aqzBjiVBbsOtZLTY4Iy5rwuB2s7pXsy3ZVh+1uibaRVtbkKUPK6+kabXXDneOXy+XyDQAA//8DAA1lMYT0BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjz+0M3w25Rw02ziI226G1EjmIiWmlBUZsGxv73gvIWiXvrjRhwhi+feWdeyQxHJD25PZ1+OuNcVtKSkvaQYMJQZijjz5z1zPZMd5pp5rX5i7QkPeI30hOrQuDBI5VokKrINB3kGGiM6DnjkQmtak34vJL+lUDqIp5TwG5Zf8e8pmUmPfkjzXH5Wt+G1IVp2WKXlwkf5rXAHKqY3x7uHumvt4fnj4e3WyuuNblLkfTEgxrQmUidswNVjCN1wTsqgHPrJCrvqwhIXZoL5hmmbo1fvr8flzJjCb3ohOwYFZ5cWhKhQJU8wIrP305VxY7ovsbbPbwf+1dyxkx60f4L863cTZjSzboNE55xOkFeMd+E5XiEOZKWnPJywlwS7ljOmIdlTaUSDcGCc5xHB1ZpG53UQtooGUbHrI4+ejDKjCJoPcbBeatGFQVX0aBSgyUt2cFNE5S0zM1V+92UcC6kJR+fmsMEZVzy8R36pzeiD/8DaJVNeiKZlsZaVFJK8GYQDKM1wRgxBGB2HATygEx7ZT23jEsvmJfaWGcjMB9ISz5AeEkzNg/3pCeMo4jSIR0FN1S5CBS09NQYaUbkwVvBSUvulrmkeVu2tXmYC37O+39JT36GacW3n97m8JIKhrLlCuFPU2Uf8hK3UJrvBuCsY51krCZt5bSV5hPGlDEUrDie83at958UYcSedLfljHNpDlBeml9gfdkZusDBgwcwIYyoGY4sMjDMRjTRYFQi2qi8ikZ5FoyCgasImg9jjZCWYPVbs1vcOmYAnKRMiJGq0RoKSo5UKxiUAe/C4N9xbD5ONQ+CYwqjpB7HSJXnQL2SkkapwQozWivGH5BczcJZBRzx/DasuwITo8NoOBWKa6q859R5belg3eAH41CHquAR84zTD6A+pJCXdRlLc/Va8w7dp22uC6fZffc1zfTvvUf3S/iCuak9hjTX+bq29dKSI8K6ZTzu+6p/JYc6XM3zvraE7qSTLXkqkMt2uka16bj3/HK5XP4BAAD//wMAAznKIvMEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUT2/jKBT/KhZn4wLG2ETaQ5TOaKNOp1Gb2V3tDcOjQXVMhHE61ajffYXjTNLRHPZmPcN7v3+PH6hXe0ALtDwcPh2hjwPKUXRTiREmMBGY0C0lC1IvSFVI3nBZiX9RjtwdvKEFqrnWVEvAJQjA3JAKt6XV2BiQlFBDWMVTT/U8oMUPpFxh4Og0FH74C8LgfI8W6G/XG/+aZitX6M6Ppgi+g3U/RNXrBObrenWHvyw324fN6dQAQ7pcOIMWSCreQiMMbpq6xZxQwI2WDWaK0ropgUuZQChXuD5C6FVXDOblMt/42EPUC1awsiCYSfSeI6OiSpBbNcD27ZBQTBLdpno+lafPxQ90hIAWLD+LeWp3ozt3sx/a0XXmJqrwDNEGtYdXH17gqDqUo0PwBwjRwaTNw1N2gURJQQomGCEoR/dK71wP2fo2e+gSYaUbwsGUWII1mEuqsORliU1ZqZoJW9fMohzdQeihu+p673Twg7cxmyXPPsxZ+T66fvTjkK37CM9BxdO9z6obAOVoM7adG3aPoMzb1j+O6V8/donLagwB+phtVNxlf6phN/nSaKqkkkoJrS1UBCwxRAlSGxBGgOHM1IZLbgSXRAuuWsqNqmhrUwXl6GGMhzGe9f9+BWIb3H4P5oJgO2m86VS0PuzXBvrorEvWnE9Mgeq6iVU291x1Dvr4QWS0QIQCM2UD2DIqMG+MwqoqJRaiFBaoljWjJ8Gsex5/6kSsNby0VdOIVraUALN1w0ltW2K0LbU0VQs1q0ndQCM1IW3FadNKTQzQipvELngz6vhrFMrJoMexT9uZrRPtV9fj74JfFNkEH732XTLMdfD0NkTY/861ld8f/OBi4m9na78NcO/79Hfe/1mz+fLSx0sN0h5k0+pRA5WudYUr0C3mlnLctopjyyrGKC9Bihrl6NbrFwhZCphy/eTJOVMPT9ky6J2LoOMY0uR/JlInOz+fV+aiR/H103blAywPh3yu/nFMIp2anQPw4V35NqTjOz/EK8oztSfo7BnYVZwMHE9PlUtFYUwDRlDMOK0wl5LiRlY1buumla1ooNKXuGaPYFwAHad+2zCmabOyv8vlMkRnlY5DWp4vXk9xmvM5H/kV5JVtP2+fhl9ZN0dp7kRJiuzT6eGcEvS/3s339/f3/wAAAP//AwBZ8qiSKAYAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUUW/jKBDHv4rFs3EBY2z81ktvdVG322ib2zvdG4ZxiupABLh7VZXvvsJJ1XRX93Bv1piZ+c9v/vCKnNoD6tH14fD7M7gUUYmSXUKMMIGJwIRuKelJ25OmampOJO/+QSWyt/CCetRyramWgGsQgLkhDR7qUWNjQFJCDWENzzXVLqL+FSlbGXi2Giofv0GI1jvUo7+sM/577q1spSc/myr4CdYuJuV0FvNlvbrFn6832/vN6VSEmJMra1CPpOIDdMLgrmsHzAkF3GnZYaYobbsauJRZhLKVdQmCU1MVzdN7f+OTg6R7VrG6IphJdCyRUUllyYOKsH05ZBULopscL5fw8tm/omcIqGflG8xTuSs92at9HGY7mavJ73bW7VbejXY3B5Vy4xIdgj9ASBYWOL9Zp8LLZ7/b5Xrok5oioBJtIeytU9Ppxw2Map7Sg5/DQiaap/86g3qk5uRRiRaS07S0Lc7DrCYLLv2Se9H4/qHYTCqNPuw/LOnhxH79P9DfKf1oHRTrG9QjQoGZugM8Miow74zCqqklFqIWI1AtW0ZRiVbeJetmP8di7RLsztQ+6LsO+tEm0GkOeaa/RW62Cd7MOhXvC6akIlVNSE6a02FOxVcwNoBOkIfYhvlc76cUJtiS9MlO8Aue1RwCuFRsVHos/lDxccHRaaqkkkoJrUdoCIzEECVIa0AYAYYz0xouuRFcEi24Gig3qqHDmCOoRJBNViy+boUGM3YdbuuWYd4OFA/SGMwG2Q3jCHTk8AFucT/lPKU7wsHUWMJoMJdUYcnrGpu6US0TY9uy8YLT2RCUZOqXJvwzgjl76cvJ2m+zG3g+3eNFpzCmAyMoZpw2mEtJcSebFg9tN8hBdNDoPNnKu+jfQH6DMPhoU35E7qyzezX9fOR9M7cQHEwX27mzOvjox1ScbVl82NfX2eVXrFgs+t06/O9ijBuvnyAU2VjKuotlHo/H4w8AAAD//wMAFYxWiAwFAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSTwW7cOAyGX8XQ2XIkWZYt34LJLjpIuxm0QVv0Rot0R6gjD2Q5bRHMuy/kmSLt3vYm/BLJnx+pFxbgiVjPbk+nv54ppIWVLPlNUkIZLgwX8lGKXrS9lJVqbNe28gsrmb+nn6xnrXZOOku8JkNco2j4UI+OI5KVQqJQjc454evC+hcGvkJ69o6qeflIcfFzYD375APO33Nt8JWb5hWrOE+0D0uC4LKZf/a7e/729vD4cLi8WmjJwZVH1jMLeqDOIO+6duBaSOKdsx1XIGXb1aStzSbAVz4kigGmasFvr/VxToGS61Wl6kpwZdm5ZAgJsuUBFnr8ecouNkR3WS83eTv2L+yZIutV+QvmJd2Nm/yNm5+eIODN6INfjqxkpzifKCZPG46tw2mC5OdQXIvsJk8hsZI9fCgOE6Rxjk9/MPpwaX3/Pzp/B+7oAxX7O9YzIUlh3REflTRcdwgcmtpyY2ozknS2VZKVbDeH5MM6r0uxD4m+xs0l69nfMC108Xcb3dEncmmN2fpnk4vRD592M2ZBsJId4oyrS8UrbikqUdUiXz6s6bSm4j2hj+QS5Z4e43pN/58QZdQWtFtjpJCKA6Rj8QaW4waicxIsWADj3EiNoFGgACNaJIOGUCtsUVuNRlvhjIZBaoRGDmNWsu883WJbKNM4I6lDPgxkuB4Gy622AwdrEGrV0NjkiFesxcOU48B1QhPW3NKIXFsJ3Oq65lg30Coztq0af0NynbgUmTfS8+VrXBwgdoRGcqVlw7W1kne2afnQdoMdTEeNyw7uKQaafgP1zrs4L/OYiuvCFH+ge7+G/L2LbXm++8B/bCO7m903ikUeOfiQt/k65fP5fP4XAAD//wMAyKqZXSUEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227cNhD9FYGvFRXexIveXNtBF6ljI7toi76NyJHNWqYWEuUkMPzvBbV2rijQF0EYknPOnDkzTyTBA5KOnB2Pl4+Y8kJqkuMWEkxoyjRl/MBZx0zHeSM0Y9KJv0lN4jv8TDpilPfcO6QSNVIVWEt7OXgaAjrOeGCiVSUn3C6keyIQm4CP0WMzLX/gvMQpkY78GVOYPhZsiI0fpzU08zTiLi0Zki9k3u/O39Hfz24O1zenWwsu5XETA+mIA9Wj1YFaa3qqGEdqvbNUAOfGSlTOFRIQm5gyzgnGZgn3X/HDlBNm34lGyIZR4chzTQJkKJR7WPDw+VhYbBJdlHi9hbff7ok84kw6Ub+KeUr3xo/xzcPSr3EMb7Yvqclxno4454ibGtf76isJzhrWCC0YIzW5An8XE1a7i+p6LCWCt0xhkNThEKhyHKhTUtIgWzBCD8aIgdTkHc4Jx2+yXkU/T8s05OpF5Oo7nP3Z+WWCfsSC8RbGBUlNdss+QwowTgkvP6Ff8ynXYV7L8fmUckzrtC7VLmW8neHl/PX9+TrPmHJ1A/mu+g2Wu61F1nNw4AC09wO2DAcWGGhmAuqgMSgRTFBOBa0c81pBz1WAlvdDiZCa/Fo03K/e47J8ZbOZZBw3DtVLn87HiCl/JyPpCOMogrRIB8E1VTYAhVY6qrXUA3LvjOCvMAeYbzGTjgy273vmfS+U4x6EAxkGJoMenBDGgBpka5TQumUGfDBGcBWkQQ9ykMwKUpObeQqrzz+2Wm4NuJmnf9DnIhXpiGZtaz30vdCKW3AWFdfKaofGqQGYkr4H3lroe86gZd5Z4YHrMgChHb6odJluY8KLuBxH+PwNrm10I3/xAJYPoH64/naGB/w4zffvTzZu3l8eSE0+rKlshGpXLPIxJvpJl2G6WsccD3czQsBwNQX82UdYxqXaJhSCYb0znEoYLFVMCQq+FdT3ivUemfZYXlxM/h7nqjgMYipT9SXZ9b46m/1dzOjzOhd+f208rvfVzQh5mOaH7zbJN4X9KEAjJGelMwEfT9to46hDsBg0p0LxlirnOLWuNbQ3tne9ttj6otj1mo9rrj5giDP6vFX84sUN8/wO/f3PYry64MWinBWv7U9bbJP2fy2x55o8ICzrjA/btu6eyC4lnDfki/U0irt0FccxLuinFBbStUa2jRNSvjD873vWNM4I8/z8/PwvAAAA//8DAK8xVv8bBgAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSTQW/cNhCF/4rA81ImKYoidTPWKbpwUy+SRVv0NuKMYsIytaAoJ4Hh/15Qu4WT3noRhAEf571vhq8swjOxnt2ezx9eKOaF7VgOW0kJZbgwXMiTFL3oeilr1XbCafs327FwT99ZzzrtvfSOeEOGuEbR8qEZPUckJ4VEoVpd7oQvC+tfGYQa6SV4quflD0pLmCPr2Z8h4vy19IZQ+2lesU7zRIe4ZIi+mPn9sL/nv90eTw/Hy6mFliKuA7KeOdADWYPc2m7gWkji1jvLFUjZ2Ya0c8UEhDrETCnCVC/49N4f5xwp+17VqqkFV4697RhChmJ5gIVO38/FxYbortR3W3n77V/ZCyXWq92/MC/X3fgp3DwvwxomvLl8MyxPJeU5zWdKOdDGZIs5TZDDHKtrp/0UKGa2Yw+fq+MEeZzT80+gPl/yH/5H/FPp/uEb+TUT7uc1ZtYzqSXbsY/gH0Ok6nDHeiYkKWws8VFJw7VF4NA2jhvTmJGkd50qmv0cc4jrvC7VIWb6krYArGe/wLTQxfpt8o8hk89rKqn+MsXHMc24+ly985eiFnUjRBGt+bzm6hNhSOQzlXyntF7v+49EGbWJ9mtKFHN1hPxY/QrL4wbFegkOHIDxfqRW0ChQgBEdkkFDqBV2qJ1Go53wRsMgNUIrh7FU2I5RGXe1bVgroRkctFxB03INFrj1jeaGvLUo2w6oWHznWD1MRQfeCk3YcEcjcu0kcKebhmPTQqfM2HVq/AHJdfpSFMBIL5e3sjkwiJbQSK60bLl2TnLr2o4PnR3cYCy1vni+pxRp+gHUx+DTvMxjrq7LU/2E7tMay3uvtkX6GiL/ts3obvZPlKoyYwixrPd1rG9vb2//AAAA//8DAMn6CWw2BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS5Som7FO0IWbepEs0qI3ijOKCcvUgqScGMb+94LaLRKfil4IYsiZeeeZId9IsM9IBnJ7On14wZATqUn2m0kwoSnTlPEjZwPrBs4b0apWcvk3qYm/x1cykE45x51BKlEjVcBaOsrJUQA0nHFgolUlpv2WyPBGrG8AX7zDZklfMSa/BDKQP32A5XvJbX3j5mWFJi4z7kPKNrgi5o/97p7+fns4PhwutxKm4tx4IAMxVo3Ya6B9341UMY60d6anwnLe9RKVMUWE9Y0PGWOwc5Pg6Wd+WHLA7AbRCNkwKgw51wRstkXyaBMeX09FxYbortjrzbxthzfygpEMov4X5iXcjZv9zXMaVz/DzWXNNj2lbZ2sy0t8JTU5xeWEMXvc+NymhM/j/Hq06enj5U7Zpg8/0K0ZYbesIZOBcNmTmmyA5tlmv4TqqnE3ewx5O8zRh+Tdf8WSpCYPX6rDbPO0xOd3/fhywbz/H5Q/WffoA1b7OzIQxlGA7JFOgmuqerDUttJQraWekDvTCU5qsltC9mFd1lTtQ8ZvcSuJDOSjnRNe9N1G9+gzurzGUudfuiQ7xAVWl6ufveSsYY1krDit+bTm6jOCj+gyliKOcb3G+/reRWixOe3WGDHk6mDzY/WbTY9b5b3j1lhjrXZuwpbhxIBZzTpADRpBCehAGQVaGea0siNXYFs+TsVCaoJldKptWrlxndbA6Qi8pUpaoNYqRp2TTpgOnWvhHcfqYS5+1vVMIUhqcAKqDLfUKCkpyNZ2Qk9dJ6ZfkFzngbMCGPDl8u42BRqgR9CcClUUGMNpb9qOjl0/mlH32Lqi+R5jwPkXUJ+8i0taplxdJ6R6h+7zGsrfUW3T8t0H+mPr0d3injBWpcfWh/JUrm09n8/nfwAAAP//AwCG30+3ggQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSU227cNhCGX0Xg9VLmSTzozrBb1HXTLGKjLXpHcYa2EC21oKhNA2PfPaB2i8S9650w0D+Hb/7hG0n+gKQnt8fjTydMZSE7UsYtJJjQlGnK+DNnvZQ9t63uDBNC/012ZHzEr6QnRoXAg0MqUSNVwDo6yBgoADrOODDRqZrTvyykfyN+bAFPY8B2Xv7AvIxzIj35c0wwf6m1/diGaV6hzfOED2kpPoXazO8Pd4/0t9v988f95a8FlypuRyA9iTrGTmpNbScsVcwaOoho6DAohUoxGzm/yMZUMCc/tQt8/l4f5pKwhF60QraMCkfOOwK++Nry4Bd8/nqsXWyI7mt8t4W3z/6NnDCTXuz+hXlJdxOm8abMxwlPOB19XjDfhPlw8AnIjhzzfMRcRtywbJNOky/jnJprsbtpxFTIjnx8avaTL3HOh3esni4IHv4HgRPmoTYI6AHBerColJMySofaCG+t1aDc4Dkz0oKxAyDnbhARTZBBdBGCgrrQl2ke/PTrsvFzgg0WmBFaoEInlNYuMJQWY2eQgdd60DY4kNYIZcFK77znbkATnA413wcfXseEzcM96QnjKEBapFFwTZUFT30nHdVa6og8OCPqNHdzKmNa53VpHlLBl7zxIz352U8LXsjd5vA6FgxlzRXqX7oW2+cZ1lCa7w7grGWtZKyK1nJcS/MJYcwYCla8z3m95vuPRGixie7WnDGVZu/La/OLX14rFW8D966OqkOI2DGMDJjXzABq0AhKgAHlFGjlWNDKD1yB7/gQa4TsCFbDNZvHuZYQ3RBp6LSmSntBrdNIBaDq9NBxofg7js3Hqep8sEwhSOowAlWOe+qUlBRk543Q0RgRf0ByNR9nNRng6XKtWwcawCJoToXiHVXOcWpdZ+hg7OAGbbELtedHzAmnH0B9GEOelzmW5urd5h26T2uqL06z+fjLmOg/247u5/AZc1N37MdUD+y61vOOHNAva8bD9mD1b2Rfr6t53t4tYdrOcb4jT8Xnsh6vYa3artPqfD6fvwEAAP//AwAyyd9e9QQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUXW/bNhSG/4rAa1EhKYoSdZc5K2akbdzGa4fdUeShQ0QhDYpKFwT+7wNlB7Fb7GJ3whHf8/Gcl3xFXj0B6tH1fv/7M/g0oRIlt4QYYQITgQndUtLXdU9lxQQRrZB/oxK5W3hBPWq51lRLwDUIwNyQBg+11dgYkJRQQ1jDc061m1D/ipSrDDw7DVWYvkGcXPCoR9+dN+FHrq1cpccwmyqGEdZ+Ssrr3Mzn9eoWf7zebO82x1MTTFlcOYN6ZIW1TS0E7hrWYU66Fg/MtngYOAfOSWcpPcqcTxC9GqvJPL7XNyF5SLpnFasrgplEhxIZlVRueVATbF/2uYsF0U2Ol0t4+exf0TNE1LPyDeYx3ZUe3dXTNMxuNFdj2O2c362Ct243R5Vy4RLtY9hDTA4WOL85r+LLx7Db5XzogxonQCXaQnxyXo3HHzdg1Tym+zDHhcxkHv/rDOqRmlNAJVpIjuNStjgNsxod+PSL9qzw3X2xGVWyIT5dLOn+yH79P9B/UvrBeSjWN6hHhAIzdQfYMiow74zCqqklFqIWFqiWLcuaVfDJ+TnMU7H2CXYnahf9XUf94BLoNMc8018im20Tg5l1Kt4XTElFqpqQLJrTfk7FVzAugk6Qh9jG+ZTvJwkTbBF9cCP8gmc1xwg+FRuVHoo/1PSAeiRVp6mSSioltLbQELDEECVIa0AYAYYz0xouuRFcEi24Gig3qqGDzRFUIsgmKxZfQwbUDRoTagfMBwVYUc4wpQOta8qoge4CbnE3Zp3SHeFgaizBGswlVVjyusamblTLhG1bZs84bY+GoCRTPzfhnxOYk5c+H639NruB5+M9XvoUxnRgBMWM0wZzKSnuZNPioe0GOYgOGp0nWwU/hTeQ3yAOYXIpPyJfZgfZiRcH3vdyC9HDeLabT07HMAWbipMpi4ttfZ19fsOKxaA/nMf/LLa4CfoRYpFtpZw/W+XhcDj8CwAA//8DAEHbGBYKBQAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUTW/bOBD9KwLPpkJSFPUB7MFwWqyRpjESd3exN4ocxkRk0qAop0GR/76gLNdO0cPehNF8vHnvDX8gJ/eAWrQ8HD4dwcUBLVC0U4gRJjARmNAtJW1RtLTJeVUwRot/0QLZO3hDLaq4UlQ1gAsQgLkmJe4Ko7DW0FBCNWElTz3l84DaH0jaXMPRKsj98BeEwXqHWvS3ddq/ptnS5qr3o86D72HthiidSmC+rld3+Mtys33YnLIGGFJxbjVqkRHGlIUQuC5ZjTmpK9wxU+Gu4xw4J7Wh9FRmXYTgZJ8P+uUyX/voIKqW5azICWYNel8gLaNMkDs5wPbtkFBMFN2m+GIKT5/tD3SEgFq2OJN5anejenuzH7rR9vomyvAM0QS5h1cfXuAoe7RAh+APEKKFiZuHp+wCiZKc5EwwQtAC3Uu1sw6y9W320KeFpaoJB13gBozGvKESN7wosC5KWTFhqooZtEB3EBz0V13vrQp+8CZmM+XZhzkr76J1ox+HbO0iPAcZT3WfZT8AWqDN2PV22D2C1G9b/zimf27s0y6rMQRwMdvIuMv+lMMOtaiRtaKykY2UQikDJQFDNJGCVBqEFqA505XmDdeCN0QJLjvKtSxpZ1IELdDDGA9jPPP//QrENtj9HvQFwXbieNPLaHzYrzW4aI1N0pwzJkP1/bRVNvdc9RZc/EAyahGhwHRRAzaMCsxrLbEsiwYLUQgDVDUVS45aeWfs8/iTJ2KM5oUp61p0TUcJMFPVnFSmI1qZQjW67KBiFalqqBtFSFdyWneNIhpoyXXaLng9qvirFYpJoMfRpevM1mntV+vwd8EvjGyCj175Pglme3h6GyLsf6fayu8PfrAx7W9mab8NcO9d+jvf/8zZXLz08RKDdAfZdHpV09GqqAtMea0wp5zgDiRg01W16CQpjGRogW69eoGQJYNJ6yZNzp56eMqWQe1sBBXHkCb/My11kvPz+WQufORfP21XPsDycFjM0T+OiaRTs7MBPrwr34aUvvND2iKG8cpHT9CbM64rN2k4nl4qm4JC6xq0oJhxWmLeNBTXTVnhrqq7phM1lOri1uwRtA2g4tRve5o2E/s7Wy5DtEaqOKTb+eLV5KbZnnPKryCvVPtZfRp+pdzspLkTJcmxT6d3czLQ/3o239/f3/8DAAD//wMAbueSACcGAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUUW/kJhDHv4rF8+IDjDH4LUpadZVes7qL2qovqzEMWRoHbzHO9RTtdz/h3SiX3lPf7NH8h5kf/+GFRHhC0pOr4/GnZ4x5JhuSwxoSTCjKFGX8nrO+aXrBaiV4K1TzF9mQcItfSU86aS23BmmDCql0rKVD4y11Dg1n3DHRylITHmbSvxAItcPnYLGe5t8xzWGKpCd/hOimL+VsCLUdp8XVaRpxG+cM0ZZmftte39Jfr3b3d7tz1oxzEdfBkZ545X3bKEV1KzSVTHd0EL6jwyAlSsm05/wsCzFjijDWs3t8O99NOWK2vahFUzMqDDltiIMMpeUBZrz/eixdrIhuSnyzhtfP/oU8YyK92LzCPJf7YMfwIS2RbMgxTUdMOeDKYB1rHCGHKVaXytdjwJjPqX+jzft1LqFtZ9xgUMrOOgRsG2eZE5I1qN3APZfSNmi0ZUbyllnPtADvRavB6wHJhtx9rnYjZD+lp3egP5/5bf8Hvo9gDyFitb0hPWEchWs0Ui+4olI7oNA2hirVKI/cmk4UzfUUc4jLtMzVNmZ8SOvUpCc/wzhf+rtK9hAy2rykguJPVQwzwhLtYX9Mkw8j7sO8d+hhGXPp96LdpcktNldvF8lZzeqGsVJ4ycclV5/QhYQ2Yxn0Pi2XM/8jEUqsouslJYy52kE+VL/AfCA9MaAtBwMGQFnrsWXomWOgWOdQOYVOCtc5aaRT0jCrJAxcOmj54EuEbAgW31TrlQLnjbGaU+OlotILoKbVgg5M2qHpwBgQ71hXd+Oqs5pJdA016B2VhgM1smmoa1rohPJdJ3yx+PG4z2dLvTqpGNV9R+viOM74j5gT/rPgfIZ14b1fznKHz+fNXcdQzml0ilMheUulMZxq03Z06PRgBqWxtWXwW0wRx+9ofww2TfPkc3WxYvWO/6clltenWm35JUT672qGm8k+YqqKmSDEsmwX/5w25AlhXhI+rY9X/0KOYB/hoYziMWG0uLfTEvO6n7N7fP3jb6v2QyY7nU6nbwAAAP//AwCS5D12GwUAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUXW/cKBT9KxbPxuHL2PgtmrTaUbabUTvqrvYNw6VBcWAEOG0UzX9f4ZlVm6fVviB0zbmce87BbyjoZ0ATuj2dPrxAKBm1qPitxAiTmEhM6JGSifOJkY4MsidU/o1a5O/hFU1oEMZQowBzkICFJT2euTPYWlCUUEtYL2pP/S2j6Q1p31l48Qa6mL9Cyj4GNKE/fbDxe71b+84scbVdigvsQy46mErmj/3uHv9+ezg+HC6nMuQK7rxFE3LSuZ5LiceejViQccAzcwOeZyFACDI6Si8wHwqkoJcu26ef99tYAhQzsY7xjmCm0LlFVhddKc86w/H1VFlsEt3VeruVt+30hl4goYm1/4p5aXdjFn/znOfVL/bmshadn/K2Om1KTK+oRacUT5CKh02f25zheV5ejzo/fbycqdv84QeYtYDdxTUUNCHKe9SiTaBl0cXH0Fw57hYPoWwfS/Ihe/NfvThq0cOX5rDo4mJ6fufHl4vM+/+h8idtHn2AZn+HJkQoMMtHwI5RicVoNdY9V1hKLh1QowZWMbsYig9rXHOzDwW+pW0kNKGPeslw4XebzKMvYMqa6px/yZqrQ4p2NaX56SUlHek4IRW0ltNams9gfQJToA5xTOu139f3ECbZBtqtKUEozUGXx+Y3nR/RhJQeDdVKK62lMQ56Ao5YoiUZLEgrwQpmByuUsFIoYqTQMxVW93R2tYJaBDU6zZbWQTDKBykwJUZjocYeK8MUZvMsCFdumPn4TsfmYak4bUYiwHKswFksFNVYCc6x5b0emHTDwNwvklzzQEkV2MLL5d1tDKS1I1hJMRO0x0IpikfVD3gexlnNcoTeVM73kAIsvwj1yZsUc3SluSakeSfd5zXUf0ezpeW7D/jH5tFdNE+Qmuqx9qE+laut5/P5/A8AAAD//wMAG9hLKYIEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTTW/UMBD9K5HP69R2HMfOrdqCWBXoClaAuDmeMbWaOivHKaBq/ztydhGUGxfLGvnNvI/xM4n2EUlPro/HV08Y80w2JIe1JJhQlCnK+IGzvml6wWrWGcl185VsSLjFn6QnnXSOO4O0QYVUAmvp0HhHAdBwxoGJVpae9ttM+mdiQw34FBzW0/wJ0xymSHryOUSYvpfZNtRunBao0zTiLs7ZRlfIvN9tb+nb6/3hbn9+NeNcwHUA0hOvvG8bpahuhaaS6Y4Ownd0GKREKZn2nJ9hIWZM0Y71DA9/5sOUI2bXi1o0NaPCkNOGgM22UB7sjIefx8Jiteim1Ddreb32z+QJE+nF5reZ53ZXbgxXj/OwhBGuzme280NReUzTEVMOuHqyyhxHm8MUq8uk7RgwZrIhdx+r/Wizn9LjC6M+nvXv/kP+oUx/9QPdkhG20xIz6QlvNNmQd9bdh4jV7ob0hHEU0GikXnBFpQZLbdsYqlSjPHJnOlHabaeYQ1ymZa52MeO3tAogPXltxxnP1K+Tuw8ZXV5SUfVFlV3YpwkWl6s//nNWs7phrICWfFxy9QEhJHQZi75DWi79/oEIJVbQdkkJY672Nt9Xb+x8T3pirHbcGmusVc55bBl6Bswq1gEqUAhSQAfSSFDSMKekHbgE2/LBlwrZECxxV+uGWSs19y3QwXtBZQOcGq49VUwyrTvmwbz0sbobV5zTTCI01KAHKg231MimodC0thPKd53wf1lySZ+zYjDg0/mvrAwUgEZQnArJWyqN4VSbtqNDpwczKI2tK5xvMUUc/zLqXXBpmiefq8vyVC+s+7DE8t+rdZG+h0h/rBndTO4BU1UytiGW9b7EejqdTr8AAAD//wMAMdT3xzYEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2Ikc2a5laUJSTwPC/F9TauaJAXwRhSM45c+bMPJEID0h6cnY8Xj5izAupSQ5bSDChKdOU8QNnvZS9YA0zQlut/iY1Ce/wM+lJp5zjziKVqJEqz1o6yNFR79Fyxj0TrSo54XYh/ROB0Hh8DA6befkD0xLmSHryZ4h+/liwITRumlffpHnCXVwyRFfIvN+dv6O/n90crm9OtxZcyuMmeNKTUY9jK7WmphWGKmY6Ooixo8OgFCrFzMj56VmIGVOEqVn8/Vd8P+eI2fWiEbJhVFjyXBMPGQrlARY8fD4WFptEFyVeb+Htt38ij5hIL+pXMU/p3rgpvHlYhjVM/s32JTU5pvmIKQfc1LjeV19JcNawRmjBGKnJFbi7ELHaXVTXUykRnGEKvaQWR0+V5UCtkpJ62UIn9Nh1YiQ1eYcp4vRN1qvg0rzMY65eRK6+w9mfnV9GGCYsGG9hWpDUZLfsM0QP0xzx8hO6NZ9yHdJajs/nmENc53WpdjHjbYKX89f352tKGHN1A/mu+g2WO9ITC8ZxsGABtHMjtgxH5hlo1nnUXqNXwndeWeW1ssxpBQNXHlo+jCVCavJr0XC/OofL8pXNZpJp2jhUL306nwLG/J2MpCeMo/DSIB0F11QZDxRaaanWUo/Ine0Ef4U5QLrFXKxlhmFgzg1CWe5AWJB+ZNLr0QrRdaBG2XZKaN2yDpzvOsGVlx06kKNkRpCa3KTZry7/2Gq5NeAmzf+gy0Uq0hPN2tY4GAahFTdgDSquldEWO6tGYEq6AXhrYBg4g5Y5a4QDri2owbfjF5Uu422IeBGW4wSfv8E1jW7kLw7A8BHUD9ffJnjAj3O6f3+ycfP+8kBq8mGNZSNUu2KRjyHST7pM9NU65XC4Swge/dXs8WcfYRmXaptQN9gRteVUm5ZTpUegRrZIbWfkgNJrhyXpxezuMVXFYRBimaovya731VlydyGjy2sq/P7aeFzvq5sJ8jinh+82yTeF/ShAIyRnpTMeH0/baOOovTfoNadC8ZYqazk1tu3o0JnBDtpg64pi12s+rrn6gD4kdHmr+MWLG+b5Hbr7n8V4dcGLRTkrXtufttgm7f9aYs81eUBY1oQP27bun8guRkwb8sV6GsVdvArTFBZ0c/QL6Y3kje20fSH4n9fatuk6K56fn5//BQAA//8DAPehEukZBgAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBbdOGmXsSLNuiN4gxjIVppQVGbBsb+94LaLRL3lpsw0My888w7fCOTPyLpyP3p9MsZp7yQmuRhCwkmNGWaMn7grJOyU6qR0mlnxN+kJsMTfiMdMSoEHhxSiRqpAtbSXsZAAdBxxoGJVpWa/vNCujfihwbwPARs5uVPTMswT6Qjfw0TzF9Lbz80YZxXaNI84m5asp9CEfPH7uGJ/n6/Pzzvr38tuJTkZgDSEa4VF7wHGnmIVDkhqXVaU40SgnbBeQ/XtGHKmCY/Ngt8+d4f5jxhDp1ohGwYFY5cagI++yK59wsevp2Kig3RY4nXW3j77N7IGRPpRP0fzGu5uzAOd8vaj3jG8eTTgukuzMejn4qWU5pPmPKAG5Yzpn5ehlyIhmC8tZyD9Ua1BqxshTQgGYJlpgUHzmulowhtG6G3zqioQHAFGpXqDanJBm4cfR7mqbppfxgHnDKpyfNLtR99jnM6vkP/ciW6+wmgRXaZF9ADgvVgUSknZZQOtRHeWqtBud5zZqQFY3tAzl0vIpogg2gjBAXFHx98eB0mrHaPpCOMowBpkUbBNVUWPPWtdFRrqSPy4IzgpCYP85SHaZ3XpdpNGT+nbV7SkV/9uOB10vsUXoeMIa+pQPikS7N9mmENufpuAM4a1kjGStKaT2uuPiIMCUPGguOQ1lu9/6UILbakhzUlnHK19/m1+s0vr6QjrTaiVUwF30qOIkQnWGTMOilACQxGAxohQq+UizLKNvQ+9MbGHkH2pkyIxW/VZnEJrQTDDGXQAlXCeGq5c1S5FpmyPUbQ7zhWz2PJ88EyhSCpwwhUOe6pU1JSkK03QkdjRPwByc0snJX2gOfrsW4KNIBF0JwKxVuqnOPUutbQ3tje9dpiGxypyROmCccfQH0YQpqXOebq5rXqHbqP61QenGrz3ddhov9sO3qcwxdMVdmxH6ZyX7e1XmpyRL+sCY/be9W9kX05ruqwPVvCNpwrXZOX7FNeT7ewdI2RQl0ul8u/AAAA//8DANzcjJX0BAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUTW/cNhT8KwLPS5lfokTd3HWDLpzETrxNit4o8nFNWCYXFOXUMPzfC2rX8G6CHnoTqDdv3psZ8gUF/QioR5f7/e9PEPKEVij75YgRJjGRmNAtJT3nvRB1J6WglPyNVshfwzPqUSuMoUYB5iABC0saPHBnsLWgKKGWsEaUnno3of4FaV9bePIG6jh9gzT5GFCPvvtg44/CrX1txjjbOsURNmHKOpgyzOfN+hp/vLzd3tweqiaYCrj2FvWISkEZHSx21DgsFOO4U1JiCdwaqYzS2h5gPmRIQY/1ZB/e+W3MAbLpWc14TTBT6HWFrM66jDzoCbbP+zLFItFVOV8tx8tn/4KeIKGerd7EPLS7MKO/eJyG2Y/2Yoy7nQ+7dQzO7+akcyFeoX2Ke0jZwyLObz7o9Pwx7nalH/qgxwnQCm0hPfqgx8OPK3B6HvNdnNOizGQf/qsG9UjPOaIVWpQcx4W2Oi6zHj2E/Av2hPjmrroddXYxPZ6ZdHfQfvM/pP+kzb0PUG2uUI8IBWZ5B9gxKrHorMa64QpLyaUDalTLKFqhdQzZhznOU7UJGXZH1c7mu0zm3mcweU5lp79kCdttinY2uXo3mJKa1JyQAprzfs7VV7A+gclQltim+djvJwiTbAF98CP8Is96TglCrm51vq/+0NM96lEjW9YIIoxuOAVmnGLEEdIpzqxgYFppoWXMDEIoxx1vzKDN0HZuAMuHtqwNJWTVkmtonOycpFi2ncCCcIYHRzm2rVWtsta2Q3MmbnUzFpw2HRFgOVbgLBaKaqwE59jyRrdMurZl7kSn7SEQlBT60xD+OYE9ZunzIdpvu1t4OtzjZU5pbQdWUswEbbBQiuJONS0e2m5Qg+ygMepg6BTfhPwGaYiTz+UR+TJ7KEk8K3j35RpSgPHEm0/epDhFl6tjKKszt77Oobxh1RLQHz7gf5ZYXEXzAKkqsdI+nFj5+vr6+i8AAAD//wMA0bGPpgoFAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rA81Lml/ihm2G3qOumWcRGW/Q2Ioe2EC21oKhNA2P/e0DtFol7600Y6J0ZPvPOvJEEByQ9uT0efzphKgvZkTJuIcGEpkxTxp8566XslWolU9IJ9jfZkfERv5KeGOU99w6pRI1UBdbRQUZPQ0DHGQ9MdKrmhJeF9G8ExjbgafTYzssfmJdxTqQnf44pzF9qbRhbP81raPM84UNaCiRfm/n94e6R/na7f/64v/y14FLF7RhIT7hWXPAh0Mh9pMoJSa3TmmqUwWvnHUC4yMZUMCeY2iV8/l4/zCVh8b1ohWwZFY6cdyRAgdryAAs+fz3WLjZE9zW+28LbZ/9GTphJL3b/wryku/HTeFPm44QnnI6QF8w3fj4cINVejnk+Yi4jbli2l04TlHFOzbXY3TRiKmRHPj41+wlKnPPhHaunC4KH/0HghHmoDQaEgMFCsKiUkzJKh9oIsNbqoNwAnBlpg7FDQM7dICIaL73oYvAq1IG+TPMA06/Lxs8JNtjAjNACFTqhtHaeobQYO4MsgNaDtt4FaY1QNlgJDoC7AY132td8H8C/jgmbh3vSE8ZRBGmRRsE1VTYAhU46qrXUEbl3RnCyI3dzKmNa53VpHlLBl7zxIz35GaYFL+Rus38dC/qy5gr1L12L7fMcVl+a7w7grGWtZKyK1nJcS/MJw5jRF6x4n/N6zfcfidBiE92tOWMqzR7Ka/MLLK+kJ502olNMeegkR+GjEywyZp0UQQn0Rgc0QvhBKRdllJ0fwA/GxgGDHEx9IVbDNZvHQYFTAwTqFTCqBHYUGNeUu6i7LqIG9p5j83HadN4yhUFShzFQ5ThQp6SkQXZghI7GiPgDkqv5OKvlA54u27p1oEOwGDSnQvGOKuc4ta4zdDB2cIO22HlHduQRc8LpB1AfRp/nZY6luXq3eYfu05rqxWk2H38ZE/1nm9H97D9jbuqMYUx1wa5jPe/IAWFZMx62g9W/kX3druZ5u1vCtpwrvSNPBXJZj9ewdK2RQp3P5/M3AAAA//8DAEQ8Blv1BAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRIp6c20HXaSOjeyiLfo2JEc2a5laUJSTwPC/F9LauaJAXwRhyJlzeObMPJEED0h6cnY8Xj5iKjOpSYlbSDChKdOU8QNnvZS9ahttOmG5+ZvUJL7Dz6QnRnnPvUUqUSNVgbXUycHTENByxgMTrVprwu1M+icCsQn4GD020/wH5jlOifTkz5jC9HHFhtj4cVpCk6cRd2kukPxK5v3u/B39/ezmcH1zujXjvCY3MZCecK244C7QgfuBKisk7azWVKMMXltvAcIpLaaCOcHYzOH+K36YSsLie9EI2TAqLHmuSYACK2UHMx4+H1cWm0QXa7zewttv/0QeMZNe1K9insq98WN88zC7JY7hzfYlNTnm6Yi5RNzUuN5XX0lw1rBGaMEYqckV+LuYsNpdVNfj+kTwHVMYJLU4BKosB2qVlDTIFozQgzFiIDV5hznh+E3Vq+jzNE9DqV5Err7D2Z+dXyZwI64Yb2GckdRkN+8LpADjlPDyE/qlnGod8rIen0+pxLRMy1ztUsHbDC/nr/nnS86YSnUD5a76DeY70pNWG9Eqpjy0kqPwgxVsYKyzUgQl0Bsd0AjhnVJ2kINsvQPvTDc4DNIZTmry66rhfvEe5/krm80k47hxqF76dD5GTOU7GUlPGEcRZId0EFxT1QWg0EpLtZZ6QO6tEV9gDpBvsZCeDJ1zjnnvhLLcg7Agw8Bk0IMVwhhQg2yNElq3zIAPxgiugjToQQ6SdYLU5CZPYfHlx1bLrQE3efoHfVmlIj3RrG07D84JrXgHtkPFteq0RWPVAExJ74C3HTjHGbTM20544NqCcqEd7Cv9y3QbE17E+TjC529wu0Y38hcP0PEB1A/X32Z4wI9Tvn9/snHz/vJAavJhSetGqHarRT7GRD/pdaKvlrHEw11GCBiupoA/+wjXcam2CTXMtuC0pO2gJVUDN9SBZNSABoZaCGCO1ORi8veYq9VhENM6VV+KXe+rs+zvYkFflrzy+2vjcb2vbkYow5Qfvtsk3zzsRwEaITlbOxPw8bSNNo46hA6D5lQo3lJlLaedbQ11pnPW6Q5bvyp2vZTjUqoPGGJGX7YXv3hxwzy/Q3//sxivLnixKGer1/anLbZJ+7+W2HNNHhDmJePDtq37J7JLCfOGfLGcRnGXruI4xhn9lMJM+k7wRgshXwj+5zUlG6Vs9/z8/PwvAAAA//8DADP4f00ZBgAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUy27cOBD8FaHPosyXKFE3Y5xgB96sB8kgG+RGka2YsEwNSMqJYfjfF9TMwvFpsReCoFjd1VVFvUAwjwgDXJ9OH54w5AQ1ZL8dccoVoYpQdmR0EGKQbaM6xgTrvkMN/hafYYBOWsusRiJQIZGOtmQUkyXOoWaUOcpbWWqaHwmGFzC+cfjkLTZL+oox+SXAAH/74JafpbfxjZ2X1TVxmXEfUjbBFjJ/7Xe35M/rw/HucL6VMBVw4x0MwJRknI2OTMxORGouSK+VIgqFs0pbbYw7w3zIGIOZm+Qe3vq7JQfMduANFw0lXMNrDc5kUyiPJuHx+VRYbBLdlPN6O962wws8YYSB1/+KeS53ZWd/9ZjG1c/u6rxmkx7Stk7G5iU+Qw2nuJwwZo+bPtcp4eM4Px9Nevh4vlO26cMvtGtGt1vWkMvEooUaNoHm2WS/hOrCcTd7DHn7mKMPydv/qiWghrsv1WE2eVri4zs/vpxl3v8PlT8Ze+8DVvsbGIAy5E70SCbOFJG9M8S0QhOlhJqQWd1xBjXslpB9WJc1VfuQ8UfcRoIBPpo54ZnfdbT3PqPNayxzflMlV4e4uNXm6s1LRhvaCEoLaM2nNVef0fmINmMZ4hjXS72v7yFc8Q20W2PEkKuDyffVHybdwwCt6ngrqbSmFQy5nTSnE6W9FtxJjrZTDjvO7SilnsQkWjsaO3b9NKITY1cmxBKdaktrL0Y3Wm1IK7QjUlBO+l51BC3VveiZQlksedOxupsLztieSnSCaJwckZoZoqUQxInWdFxNXcen3yS55IHR0t7h0/ndbQyUcz06xQiXrCVSa0Z63XZk7PpRj6rH1mqo4RZjwPk3oT55G5e0TLm6JKR6J93nNZR/R7Wl5acP5Nfm0c1iHzBWxWPjQ3kqF1tfX19f/wEAAP//AwD694M2ggQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUyW7jOBD9FYFnU+GmFZiD4XRjjHQ6RuKeGcyNSzEmIosGRSUdBPn3AWU5dhp9mJtQquXVe6/4hnq5B9Si5eHw5Rn6OKAFim4KMcJKTEpM6JaSlvNWFDmpalKV7F+0QO4GXlGLKqE11Q1gDiVgYUiBFbcaGwMNJdQQVojUUz4OqH1D0uUGnp2G3A9/QRic71GL/na98S9ptnS57vxo8uA7WPdDlL1OYL6vVzf423KzvdscswYYUnHuDGoRLQVlVBlsqbZYNIzjuilLXAI3umx0I6U5lrk+Quhllw/m6Tzf+NhD1C3LGc8JZg16XyAjo0yQlRxg+3pIKCaKrlN8MYWnz/YNPUNALVucyDy2u9Kdu9oPanSduYoyPEK0Qe7hxYcneJYdWqBD8AcI0cHEzd1DdoZESU5yVjJC0ALdSr1zPWTr6+yuSwtLXRMBhuMGrMGioRI3gnNseCErVtqqYhYt0A2EHrqLrrdOBz94G7OZ8uzTnJXvo+tHPw7Zuo/wGGQ81n2V3QBogTaj6tywuwdpXrf+fkz/+rFLu6zGEKCP2UbGXfanHHaoRUVZsUIQoWXBKTBtG0YsIXXDmREMdFUaqBjTSojGcssLraRWVW0VGK4qihboboyHMZ74/3kBYhvcfg/mjGA7cbzpZLQ+7NcG+uisS9KcMiZDdd20VTb3XHUO+viJZNQiQoEZXgO2jJZY1EZiWfAGlyUvLVDdVCyhW/neusfxgydirRHcFnVdqkZRAsxWtSCVVcRoy3VjCgUVq0hVQ91oQlQhaK0aTQzQQiSTboI3o46/WoFPAt2PfbrObJ3WfnE9/lmKMyOb4KPXvkuCuQ4eXocI+9+ptvL7gx9cTPvbWdofA9z6Pv2d73/mbC5e+niOQbqDbDo9QutCCEWxqlWBRWUKrIxKN2SJpFaQknO0QNdeP0HIksGk6ydNTp66e8iWQe9cBB3HkCb/My11lPPr6WTOfOTfv2xXPsDycFjM0T+eE0nHZicDfHpXfgwpfeeHtEUM44WPHqCzJ1wXbjLwfHypXAqWxtRgSoqZoAUWTUNx3RQVVlWtGlXWUOjmw63ZPRgXQMep3/Y4bSb2d7Zchuis1HFIt/PN68lNsz3nlF9BXqj2UX0cfqHc7KS5EyXJsQ/Hd3My0P96Nt/f39//AwAA//8DADQAEnAnBgAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTS2/UMBD+K5HPcepXHDu3agtiVaCrsgLEzY8JtZo6K8fpQ9X+d+TsogI3LpY18jfzPcavKJoHQD26PBzePULMM6pRDmuJESYxkZjQPSU9571oG9kJSbT8gWoUruEF9agTzlGnAXOQgIUnLbZ8cNh70JRQT1grSk/zc0b9KzKh8fAYHDTT/BXSHKaIevQtRD89ldkmNG6cFt+kaYRtnLOJrpD5vN1c44+Xu/3N7vRqhrmAm+BRj6gUlFHr8UDdgIVmHCstJZbAvZPaaWP8CRZihhTN2Mz+/m2+n3KE7HrWMN4QzDQ61sibbApla2bYvxwKi9Wiq1Kv1/J67V/RIyTUs/q3mad2F24MFw+zXcLoL05nNvN9UXlI0wFSDrB6ssocR5PDFKvzpM0YIGZUo5sv1W40eZjSw19GfTnp3/6H/H2Z/u4Z3JLBb6Yl5oLlCtXok3F3IUK1vUI9IhSY5wrwwKjEQnmDTcs1lpLLAajTHaOoRpsp5hCXaZmrbczwM60CUI/em3GGE/XL5O5CBpeXVFR9l2UXdmnyi8vVm/+UNKThhBTQkg9Lrm7BhwQuQ9G3T8u53z8QJtkK2iwpQczVzuS76oOZ71CPWtmxVhDhTMspMDdoRgZClObMCwaukx46xpwVQg984K2zxtlODRY8t11RCCXuat0wq6yyzigMjA1YUK+w9lRh3jLBtQXlbaH45mN1MxaccYoI8BxrGDwWmhqsBefY89Z0TA5dx4Y/LDmnT0kZ7+Hx9FdWBtJ7BV5SzARtsdCaYqXbDttOWW2lgtZpVKNrSBHGP4z6FFya5mnI1Xl5qr+su11i+e/VukhPIeLnNaOryd1DqkrGJsSy3udYj8fj8RcAAAD//wMAYDGxDjYEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAs6jwS5Som2G36MJNvUiMtuhlQQ2HXtYypVKU08DY/x5Iu4bj5tSbNOA7Hw/f4QuJ9glJR66m6adnjHkmJclhCwkmNGWaMn7PWSdlp3QlhOGNaf4iJQm3+JV0pFEAHAxSiRqpcqymvfRAnUPDGXdM1GrNaR9m0r0QGyqHzwGwGuffMc1hjKQjf4Toxi9rbRsqGMbFVWkccBfnbCOszfy2u76lv17t7+/251Mzzqu4Co50hGvFBe8d9Rw8VUZI2hqtqUbpQBsw1rqzLMSMKdqhmt3jW3035ogZOlEJWTEqDDmVxNls15Z7O+P912ntYkN0s8bLLbx9di/kGRPpRPkK85zuAwzhQ1oiKcmUxglTDrgx2MYaBpvDGItL5ushYMzno38j5MM2l2ihMa43qFQDDi3W0gFzQjGJreu550qBRNMCM4rXDDxrhfVe1K31bY+kJHefi/1gsx/T0zvQn8/8dv8D30cLxxCx2N2QjjCOwskWqRdcU9U6S20tDdVaao8cTCM4Kcn1GHOIy7jMxS5mfEjb1KQjP9thvvR3leAYMkJe0oriT70aZrBLhONhSqMPAx7CfHDo7TJk0hF/0e7T6BbIxdtFclaxSjK2Jl7ytOTiE7qQEDKug96n5VLzPxKhxSa6XlLCmIu9zcfiFzsfSUdq3YhaMQW2lhwFeCOYZ6w1UjglEBrtsBECeqWMl17W0Fvom9b36GTfrBRw9U2xXallvbTgDXWt9lS5VlNbO0elbrzpWb9e9TvWxd2w6aBlCp2kBr2jynBLjZKSOlnbRmjfNMKvFp+mQz5b6tVJq1Hdd7QujuOM/4g54T8LzmdYF96H5Sx3+Hze3G0M7VyLTnMqFK+pMobT1tQN7Zu2N71usQZDSnKLKeLwHe2PAdI4jz4XFysW7/h/WuL6+hSbLb+ESP/dzHAzwiOmYjWTDXFdtot/TiV5QjsvCZ+2x6t7IZOFR/uwjuIxYQQ8wLjEvO3n7B5f//jbqv1wkp1Op9M3AAAA//8DAEDxRpkbBQAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTXW/UMBD8K5Gf49RfceK8VVcQpwI90QoQb4695ixS++Q4pVV1/x05d6gtb7xZK8/s7MzuMwr6HtCALg+Hdw8Q8oxqlP1aYoRJTCQm9I6SgYuB8aYTnWS9+IFq5K/hCQ2oE8ZQowBzkICFJS0euTPYWlCUUEtYKwqn/jmj4Rlp31h48AaaOH+FNPsY0IC++WDj79Jb+8ZMcbFNihNsw5x1MEXM5+3mGn+83N3d7E6/ZpgLuPEWDYhKQRkdLXbUOCwU47hXUmIJ3BqpjNLanmA+ZEhBT81sf730tzEHyGZgDeMNwUyhY42szrpIHvUMd0+HomK16KrU67W8Podn9AAJDaz+a+aJ7sJM/sLE+3sd7IXzwc97VKNDigdI2cNqxzrhNOnsY6jOTTaTh5BRjW5uq92ks4vp/o1Ht6fRt/8x+Sdt9j5Atb1CAyIUmOU9YMeoxKK3GuuWKywllw6oUR2jqEabGLIPS1zmahsy/EyrSjSg93qa4aTvMpm9z2Dykor077JkDY8+b6ItBVyIdinaxeTqxW9KGtJwQgrJkg9Lrr6A9QlMhjLUXVrO/P9AmGQraLOkBCFXO5331Qc979GAWtmxVhBhdMspMOMUI46QXnFmBQPTSQsdY2YUQjnueGtGbcaudyNYPnZFKJR4q3WjBG1BqI5gSjXFQlCLR2dHPBrRgRRC9oa/8bW6mQpOm54IsBwrcBYLRTVWgnNseas7Jl3XMffKknPklJT2Fh5Ot7EqkNb2YCXFTNAWC6Uo7lXb4bHrRzXKHlqjUI2uIQWYXhn1yZsU5+hydd6Y6o11X5ZQ7rtat+e3D/hxzewqml+QqpK59qGs8znm4/F4/AMAAP//AwBbRwxXJgQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUTW/bOBT8KwLPosIvUaJuWWeLNdI2buNtF3ujyCeHiEIaFJVuEPi/Lyg7iN1iD3sTqDdv3psZ8hV5/QSoQ9f7/e/P4NOESpTccsQIk5hITOiWko6LjotK1aJumfwblcjdwgvqUCOMoUYB5iABC0tq3PPBYGtBUUItYbXIPfVuQt0r0q6y8OwMVGH6BnFywaMOfXfehh+ZW7vKjGG2VQwjrP2UtDd5mM/r1S3+eL3Z3m2OVRNMGVw5izpEWMsk1TWGVrRYSAZYi7bGmtp2kIY3qrVHmPMJotdjNdnHd34bkodkOlYxXhHMFDqUyOqk88i9nmD7ss9TLBLd5PNyOV4+u1f0DBF1rHwT89juyozu6mnqZzfaqzHsds7vVsEPbjdHnTJxifYx7CEmB4s4vzmv48vHsNvlfuiDHidAJdpCfHJej8cfNzDoeUz3YY6LMpN9/K8a1CE9p4BKtCg5jgttcVpmNTrw6RfsGfHdfbEZdRpCfLow6f6o/fp/SP9JmwfnoVjfZAwFZnkLeGBUYtFajXXNFZaSywGoUQ2jqESr4JPzc5inYu0T7E6qXcx3Hc2DS2DSHPNOf8kctk0MdjapeDeYkopUnJAMmtN+TsVXsC6CSZCX2Mb51O8nCJNsAX1wI/wiz2qOEXwqNjo9FH/o6QF1qJYNqwURRtecAjODYmQgpFWcWcHANNJCw5jphVADH3htem36ph16sLxv8tqQQ1YsuW6bnnLW11jZAbBg1OC25g1WhA4EgNZCX4pb3I0Zp01LBFiOFQwWC0U1VoJzbHmtGyaHpmHDmU7bYyAoyfTnIfxzAnvK0udjtN92t/B8vMfLnNLaFqykmAlaY6EUxa2qG9w3ba962UJt1NHQKbwJ+Q1iHyaX8iPyZXaQk3hR8O7LLUQP45k3n5yJYQpDKk6hLC7c+jr7/IYVS0B/OI//WWJxE8wjxCLHSjt/ZuXhcDj8CwAA//8DAGAI+UoKBQAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSU227cNhCGX0Xg9VLm+aA7w25R102ziI226B3FGdlCtNSCojYNjH33gNotEveud8JA/xy++YdvJIUDko7cHo8/nTCVhexIGbeQYMJQZijjz5x1UnVStdJrzZ39m+zI+IhfSUesipFHj1SiQaqAadrLIVIA9JxxYEKrmjO8LKR7I2FsAU9jxHZe/sC8jHMiHflzTDB/qbXD2MZpXqHN84QPaSkhxdrM7w93j/S32/3zx/3lrwWXKm5HIB1hwgnDg6bolKPKCKRBOU0DBzeYKK13cJGNqWBOYWoX+Py9PswlYYmdaIVsGRWenHcEQgm15T4s+Pz1WLvYEN3X+G4Lb5/dGzlhJp3Y/Qvzku4mTuNNmY8TnnA6hrxgvonz4RBS7eWY5yPmMuKGZZt0mkIZ59Rci91NI6ZCduTjU7OfQhnmfHjH6umC4OF/EDhh7muDgAEQXACHSnkpB+nRWBGccwaU7wNnVjqwrgfk3PdiQBtlFHqAqKAu9GWa+zD9umz8vGC9A2aFEajQC2WMjwylw0FbZBCM6Y2LHqSzQjlwMvgQuO/RRm9izfchxNcxYfNwX6fhKEA6pIPghioHgQYtPTVGmgF59FZwsiN3cypjWud1aR5SwZe88SMd+TlMC17I3eb4OhaMZc0V6l+mFtvnGdZYmu8O4KxlrWSsitZyXEvzCWHMGAtWvM95veb7j0QYsYnu1pwxlWYfymvzS1heSUe0sUIrpmLQkqOIgxdsYMx5KUAJjNYAWiFir5Qf5CB17EPsrRt6BNnbOiFWwzWbxwNoqFug3EpDlWKW9gw0BSkE9zZoJcU7js3HadNFxxSCpB4HoMrzQL2SkoLUwQozWCuGH5BczcdZLQ94ulzr1oEBcAiGU6G4psp7Tp3XlvbW9b43DnX0ZEceMSecfgD1YYx5XuahNFfvNu/QfVpTfXGazcdfxkT/2XZ0P8fPmJu64zCmemDXtZ535IBhWTMetgereyP7el3N8/ZuCdUyy/WOPJWQy3q8hqVtjeL+fD6fvwEAAP//AwAL3gdL9QQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rA81Lm94duht2ihpt6ERtt0BvFGcZCtNKCojYNjP3vBbVbJO4tN2GgmXnnmXf4RqZwQNKR2+PxlxNOZSE7UoYtJJgwlBnK+AtnnVSdVK2Smisv/yY7MjziN9IRq2Lk0SOVaJAqYJr2MkUKgJ4zDkxoVWuGzwvp3kgYWsDTELGdlz8xL8M8kY78NUwwf629w9DGcV6hzfOID9NSwhSrmD8e7h7p77f7l6f95a8Fl5rcDkA6woQThgdN0SlHlRFIg3KaBg4umSitd3BJG6aCeQpju8CX7/1hLhOW2IlWyJZR4cl5RyCUUCX3YcGXb8eqYkN0X+O7Lbx9dm/khJl0YvcfzEu5mzgON8vaj3jC8RjygvkmzodDmKqWY56PmMuAG5YT5n5ehlKJxmiDc5yDC1ZpC05qIS1IhuCY1eDBB6NMElHrBL3zViUFgiswqFRvyY5s4MYxlGGemqv2u3HAqZAdeXpu9mMoac6Hd+ifL0QffgJolV3nBQyA4AI4VMpLmaRHY0VwzhlQvg+cWenAuh6Qc9+LhDbKKHSCqKD640OIr8OEzcN97c5RgHRIk+CGKgeBBi09NUaahDx6KzjZkbt5KsO0zuvSPEwFP+dtXtKRX8O44GXS2xxfh4KxrLlC+GRqs32eYY2l+W4AzlrWSsZq0lqOa2k+IgwZY8GK4yWv13r/SxFGbEl3a844lWYfymvzW1heSUe0sUIrpmLQkqOIyQuWGHNeClACozWAVojYK+WTTFLHPsTeutQjyN7WCbH6rdkszrkLyUdHpWOCKsk1dazXNCaRvHEKjO7fcWyexpoXomMKQVKPCajyPFCvpKQgdbDCJGtF+gHJ1Syc1faAp8uxbgoMgEMwnArFNVXec+q8trS3rve9caijJzvyiHnC8QdQH4aY52VOpbl6rXmH7uM61Qen2Xz3dZjoP9uO7uf4BXNTdxyGqd7Xda3nHTlgWNaMh+296t7Ivh5X87I9W0K1zHK9I88l5LIer2FpW6O4P5/P538BAAD//wMAXUY/gfQEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUTW/bOBD9KwLPokKRlEQa2IPhtFgjTWMk7u5ibxQ5jInIokFRSYMg/31BWY6dooe9CaP5ePPeG76hXu0BLdDycPjyDH0cUI6im0KU0BqTGpNyW5IF4wtWFSWvqobKf1GO3A28ogVquNalloAZ1IC5IRVumdXYGJAlKQ2hFU891eOAFm9IucLAs9NQ+OEvCIPzPVqgv11v/EuarVyhOz+aIvgO1v0QVa8TmO/r1Q3+ttxs7zbHrAGGVFw4gxaIUEHrUlUYBBeY1xSw4qLCqjTC1po1UphjmesjhF51xWCezvONjz1EvaAFZQXBVKL3HBkVVYLcqgG2r4eEYqLoOsXzKTx9Lt7QMwS0oPmJzGO7K925q/3Qjq4zV1GFR4g2qD28+PAEz6pDOToEf4AQHUzc3D1kZ0glKUhBa0oIytGt0jvXQ7a+zu66tLDSgnAwDEuwBnNZKiw5Y9iwSjW0tk1DLcrRDYQeuouut04HP3gbs5ny7NOcle+j60c/Dtm6j/AYVDzWfVXdAChHm7Ht3LC7B2Vet/5+TP/6sUu7rMYQoI/ZRsVd9qcadmiBqrqhFSdcq4qVQLWVlFhChGTUcAq6qQ00lOqWc2mZZZVulW4bYVswrG1KlKO7MR7GeOL/5wWIbXD7PZgzgu3E8aZT0fqwXxvoo7MuSXPKmAzVddNW2dxz1Tno4yeSk6NKoIYJwJaWNebCKKwqJnFds9pCqWVDE7qV7617HD94ItYazmwlRN3KtiRAbSM4aWxLjLZMS1O10NCGNAKE1IS0FS9FKzUxUFY8mXQTvBl1/NUKbBLofuzTdWbrtPaL6/HPmp8Z2QQfvfZdEsx18PA6RNj/TrWV3x/84GLa387S/hjg1vfp73z/M2dz8dLHcwzSHWTT6cmqrbWgFeYiOVFzi6UFiQ0YyTTRpCUM5eja6ycIWTKYcv2kyclTdw/ZMuidi6DjGNLkf6aljnJ+PZ3MmY/i+5ftygdYHg75HP3jOZF0bHYywKd35ceQ0nd+SFvEMF746AE6e8J14SYDz8eXyqVgbYwAU5eY8rLCXMoSC1k1uG1EK9taQKXlh1uzezAugI5Tv+1x2kzs72y5DNFZpeOQbueb15ObZnvOKb+CvFDto/o4/EK52Ulzp5Ikxz4c383JQP/r2Xx/f3//DwAA//8DACepZYcnBgAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUTW/cNhD9KwLPoswv8UM3Y52gCzf1IlmkRW8UOYoJy9SCpJwYxv73gtotEp+KXghiyDfz5s0j31C0z4AGdHs6fXiBWDJqUQlbiBEmMZGY0CMlAxcD7zslBFHa/I1aFO7hFQ1ICeeoM4A5SMDCkx6PfHLYezCUUE9YL2pO+y2j4Q3Z0Hl4CQ66JX+FlMMS0YD+DNEv32ttGzo3L6vv0jLDPuZio6tk/tjv7vHvt4fjw+FyK0Ou4C54NCDCNJPU9hi00FhIBtgK3WNLvZ6k48pof4GFWCBFO3fZP/2s75cSobiBdYx3BDODzi3ytthKebQZjq+nymKT6K7G2y28bYc39AIJDaz9V8xLuhs3h5vnPK5h9jeXtdj8lLd1sq4s6RW16JSWE6QSYNPnNmd4HufXo81PHy936jZ/+AFuLeB3yxoLGhDlPWrRJtA82xKW2Fw57uYAsWyHJYWYg/uvXBy16OFLc5htmZb0/G4eXy4y7/+Hyp+sewwRmv1dxVBgnmvAE6MSC+0ttj03WEouJ6DOKEZRi3ZLLCGuy5qbfSzwLW0toQF9tHOGC7/b5B5DAVfWVPv8S1ZfHdLiV1ean7OkpCMdJ6SC1nJaS/MZfEjgCtQmjmm95vv6HsIk20C7NSWIpTnY8tj8ZvMjGlAvFesFEc72nAJzk2FkIkQbzrxg4JT0oBhzoxBm4hPv3WjdqPQ0guejqh1CtU6zuZV7TaRmEybKeSy0NXgcqcKCKuJ7KrkY3+vYPMwVZ50mAjzHBiaPhaEWG8E59ry3islJKTb9IsnVD5TU8h5eLu9uYyC91+AlxUzQHgtjKNamV3hUejSj1NA7g1p0DynC/ItQn4JLS16m0lwd0ryT7vMa69/RbG75HiL+sc3obnFPkJo6YxtifSrXsZ7P5/M/AAAA//8DAKDmvtCCBAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRenNtR10kTo2sou26NuQHNqsZWohUU4Cw/9eSGvnigJ9EQRyOOfMmTPzRDI8IOnJ2fF4+Yi5zKQmJW1HgglDmaGMHzjrpeqlblotmBH8b1KT9A4/k560ynvuO6QSDVIVmKZORk9DwI4zHpjQas0JtzPpnwikJuBj8tiM8x84zWnMpCd/phzGjys2pMYP4xKaaRxwl+cC2a9k3u/O39Hfz24O1zenqBnn9XGTAukJE1YYDpqiVZYqI5CCspoCDzYaL9vOhtOzlAtOGYZmDvdf8cNYMhbfi0bIhlHRkeeaBCiwUnYw4+HzcWWxSXSxntfb8fbbP5FHnEgv6lcxT+ne+CG9eZjdkobwZvuSmhyn8YhTSbipcb2vvpLgrGGNMIIxUpMr8HcpY7W7qK6HtUTwlikMknYYA1UdB9opKWmQGlphYtuKSGryDqeMwzdZr5KfxnmMpXoRufoOZ392fpnBDbhivIVhRlKT3bwvkAMMY8bLT+iXcsp1mJb1+nzMJeVlXOZqlwveTvBy//r+fJkmzKW6gXJX/QbzHemJNq3QiikPWnIUPnaCRcZsJ0VQAn1rArZCeKdUF2WU2jvwrrXRYZCu5aQmv64a7hfvcZ6/stlMMgwbh+qlT+dDwly+k3F1CUcRpEUaBTdU2QAUtOyoMdJE5L5rxReYA0y3WEhPonXOMe+dUB33IDqQITIZTOyEaFtQUepWCWM0a8GHthVcBdmiBxkls4LU5GYaw+LLj62WWwNupvEf9GWVivTEMK2tB+eEUdxCZ1Fxo6zpsO1UBKakd8C1Bec4A818Z4UHbjpQLujYvdK/zLcp40WajwN8/gbXNqaRv3gAyyOoH8LfTvCAH8fp/v3Jxs37ywOpyYclrxuh2q0W+Zgy/WTWib5ahpIOdxNCwHA1BvzZR7iOS7VNqLNMaS0k5SpGqkIbqRNoqHSOgXeOn1p8Mfp7nKrVYZDyOlVfkl3vq7PJ36WCvizTyu+vjcf1vroZoMRxevhuk3xT2I8CNEJytnYm4ONpG20cTQgWg+FUKK6p6jpObadb6lrrOmcsar8qdr2U41KqDxjShL5sFb94ccM8v0N//7MYry54sShna7370xbbpP1fS+y5Jg8I8zLhw7at+yeyyxmnDfliOY3iLl+lYUgz+jGHmfSW6UbKVr8Q/M8wYRothX5+fn7+FwAA//8DAOIV/fMZBgAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZbpG7GOIsdeBMPkkF2sTeKbMaEZWpAUU4CY/49oGaCxHvbC0E0WF1d1cVXlOwzoAHdnk7vXiCVBbWoxK3ECFOYKEzokZKBi4HLrhe9ZtL8i1oU7+E7GlAvnKPOAOagAAtPJB55cNh7MJRQT5gUtaf9sqDhFdnYeXiJDrp5+Qx5iXNCA/o7Jj9/rdw2dm6aV9/leYJ9WopNrg7zYb+7x3/dHo4Ph8urBZYK7qJHAyJMM0WtxKCFxkIxwFZoiS31OijHe6P9BRZTgZzs1C3+6Re/n0uC4gbWMd4RzAw6t8jbYuvIo13g+P1Up9gsuqv1ditv1+EVvUBGA2t/mnlpd+OmePO8jGuc/M3lLHZ5qipPeT5BLhE2TzaZ02RLnFNzZdpNEVJBLXr41BwmW8Kcn98Y9emif/8/5B8r+7tv4NYCfjevqaABUa5Ri95b9xgTNPu72o4C81wDDowqLLS32EpusFJcBaDO9IyiFu3mVGJa53Vp9qnAl7wJQAP6w04LXEa/ze4xFnBlzVXVP6pm4ZBnv7rS/PKfko50nJAKWstpLc1H8DGDK1D1HfN67fcfCFNsA+3WnCGV5mDLY/OnXR7RgKTqmRREOCs5BeaCYSQQog1nXjBwvfLQM+ZGIUzggUs3Wjf2Oozg+dhXhVDX3WwJG0kIVmmFrZIKC9pLPAYtcBDaa80dU3x842PzMFWcdZoI8BwbCB4LQy02gnPsubQ9U6HvWfjNkuv2Kan0Hl4uf2WbQHmvwSuKmaASC2Mo1kb2eOz1aEalQTqDWnQPOcH0m1Hvo8vzMofSXMPTvLHu45rqf2+2IH2NCX/bdnQ3uyfITd2xjanG+7rW8/l8/gEAAP//AwCsEOr4NgQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUy27cNhSGX0XgWlR4E0VqZ9gtOnBTDxKjLboZUOShh7VMqRTlNDDm3QNqxnDcrLqTiPOfy8f/8AVF8wSoR1fz/NMzxLygGuWwHTHCJCYSE3pPSc9Fz2XDlBRSdX+hGoVb+Ip61AlrqdWAOUjAwpEWD9xb7BxoSqgjrBUlp3lYUP+CTGgcPAcLzbT8DmkJU0Q9+iNEN30ptU1o7DitrknTCLu4ZBNtaea33fUt/vVqf3+3P0ctsBRxExzqEWGKSWpaDEooLCQDbIRqsaFOeWl5p5U7y0LMkKIZm8U9vtV3U46Qbc8axhuCmUanGjmTTWl5MAvcf51LFxuim3Jeb8fbZ/+CniGhntWvMM/pPtgxfEhrRDWa0zRDygE2BttY42hymGJ1yXw9Boj5HPo32HzY5mLKdtoNGoTorAMDLXeWOCYIB+UG6qkQloNWlmhBW2I9Ucx4z1plvBoA1ejuc7UfTfZTenoH+vOZ3+5/4Pto7DFEqHY3RUOBOa4Ae0YlFsoZbFqusZRceqBWd4yiGl1PMYe4TutS7WKGh7RNjXr0sxmXS39XyR5DBpvXVFD8KYthRrNGezzMafJhhENYDg68WceMeuQv2n2a3Gpz9XaRlDSk4YSUxGue11x9AhcS2Axl0Pu0Xmr+R8Ik20TXa0oQc7U3+Vj9YpYj6lErO9YKIqxpOQVmvWbEE6I0Z04wsJ100DFmByG05563djB26JQfwPGhKxSg+KbarpRqaRVXFvuOdFh46bBmrsXMU8U5HbiQ7TvW1d1YdMYqIsBxrME7LDQ1WAvOseOt6Zj0Xcd8sfg8H/LZUq9OKkZ139G6OI4S+iPmBP+ssJxhXXgf1rPcwfN5c7cxpHMKnKSYCdpioTXFSrcdHjo16EEqaK1GNbqFFGH8jvbHYNO0TD5XFytW7/h/WmN5farNll9CxP9uZriZ7COkqpjJhFiW7eKfU42ewCxrgqft8epf0Gzso3koo3hIEC0c7LTGvO3n4h5f/+jbqv0QSU6n0+kbAAAA//8DAO62JwsbBQAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTy27UMBT9lcjrOPUrjp1dNQUxKtARrQCxc+wbxiK1R45TWlXz78iZQW3ZsYuucs49j+tnFMw9oB5dHg7vHiDkGdUo+3XECJOYSEzoHSU9F72QDSWaKkZ/oBr5a3hCPeqEtdRqwBwkYOFIiwc+WuwcaEqoI6wVhdP8nFH/jIxvHDx4C02cv0KafQyoR998cPF32W18Y6e4uCbFCbZhzibYIubzdnONP17u7m52p79mmAu48Q71iDDFJDUtBiUUFpIBNkK12FCnRml5p5U7wXzIkIKZmtn9etnvYg6Qbc8axhuCmUbHGjmTTZE8mBnung5FxRrRVZnX63j97J/RAyTUs/pvmCe6Czv5Cxvv701wF6MPft6jGh1SPEDKHtY4VofTZLKPoTov2UweQkY1urmtdpPJY0z3bzK6PVnf/ofzT8bufYBqe1UwFJjjCvDIqMRCOYNNyzWWkssRqNUdo6hGmxiyD0tc5mobMvxMq0rUo/dmmuGk7zLZvc9g85KK9O+ydA2PPm+iKwNciHYpusXm6iVvShrScEIKyZIPS66+gPMJbIZi6i4tZ/5/IEyyFbRZUoKQq53J++qDmfeoR63sWCuIsKblFJgdNSMjIUpz5gQD20kHHWN2EEKPfOStHYwdOjUO4PjQFaFQ6q3Wi5JaDm4kHDPCOixIK7EajMGiZUzygTNGi9WXXKubqeCMVUSA41jD6LDQ1GAtOMeOt6Zjcuw6Nr6K5Fw5JWW9g4fT2zgpcE6BkxQzQVsstKZY6bbDQ6cGPUgFrdWoRteQAkyvgvrkbYpzHHN1vpjqTXRfllDed7Vez28f8OPa2VW0vyBVpXPjQznnc83H4/H4BwAA//8DAKOT5R4mBAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBb1HVTL+JFWvRGcka2EK20oKhNA2P/e0DtBol7600Y6J0ZPvPOvJHJH5B05PZ4/OWEU15ITfKwhQQTmjJNGd9z1knVtappW90qxv4hNRke8SvpiFEx8uiQStRIFbCWBtlHCoCOMw5MtKrk9C8L6d6IHxrA0xCxmZdPmJZhnkhH/hommL+U2n5o4jiv0KR5xIdpyX6KpZk/H+4e6R+3u/3T7vLXgksRNwOQjlirFbOcU9lyoKo1gTrTBxqtNFq7IKMQF9kwZUyTH5sFPv+oD3OeMMdONEI2jApHzjUBn31pOfgF91+PpYsN0X2J11t4++zeyAkT6UT9HeYl3U0ch5s8H0c84Xj0acF0E+fDwU9AanJM8xFTHnDDsr10HH0e5qm6FrsbB5wyqcnTc7Ubfe7ndHjH6vmC4OF/EDhhCqVBQA8I1oNFpZyUvXSojfDWWg3KBc+ZkRaMDYCcuyB6NFFG0fYQFZSBvoxz8OPvy8bPCRYsMCO0QIVOKK1dZCgt9q1BBl7roG10IK0RyoKV3nnPXUATnY4l3wcfX4cJq4d70hHGUYC0SHvBNVUWPPWtdFRrqXvk0RnBSU3u5ikP0zqvS/UwZXxJGz/SkV/9uOCF3G2Kr0PGmNdUoP6tS7FdmmGNufrhAM4a1kjGimjNxzVXHxGGhDFjwbtP6zXffyRCi010t6aEU652Pr9Wv/nllXSk1Ua0iqnoW8lRxN4J1jNmnRSgBEajAY0QMSjletnLNgYfg7F9QJDBlBdiMVy1eVxhrwG4oD4yS5XqA/VRAkXjuYkxBAz9O47V01h0PlqmECR12ANVjnvqlJQUZOuN0L0xoui+I7maj7NSHvB02datAw1gETSnQvGWKuc4ta41NBgbXNAW2+hITR4xTTj+BOrDENO8zH2urt6t3qH7uE7l4lSbj78ME/13m9H9HD9jqsqM/TCVBbuO9VyTA/plTXjYDlb3RnZlu6r9drckb1Rra/Kcfcrr8RpVomklU+fz+fwNAAD//wMAh8LN9fQEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSU227cNhCGX0XgtSjzfNCdYbfowk29iBdt0DuKM4qFaKUFRW0aGPvuBbVbJO5d7oSB/jl88w/fyBSOSFpyfzr9csYpL6QmedhCgglDmaGMHzhrpWq1agxjwjn3N6nJ8ITfSEusipFHj1SiQaqAadrJPlIA9JxxYEKrkjN8Xkj7RsLQAJ6HiM28/IlpGeaJtOSvYYL5a6kdhiaO8wpNmkfcTUsOUyzN/LF7eKK/3+8Pz/vrXwsuRdwMQFrinFHMcU6l5kCVth31tu9odNIa4zsZhbjKhiljmsLYLPDle32Y84Q5tqIRsmFUeHKpCYQcSstdWPDw7VS62BA9lni9hbfP9o2cMZFW1P/BvKa7i+Nwt6zdiGccTyEtmO7ifDyGCUhNTmk+YcoDbljOmLp5GXIhGqMNznEOLlilLTiphbQgGYJjVoMHH4wyvYha99A5b1WvQHAFBpXqLKnJBm4cQx7mqbr1/jAOOGVSk+eXaj+G3M/p+A79y5Xo7ieAlrbLvIABEFwAh0p5KXvp0VgRnHMGlO8CZ1Y6sK4D5Nx3okcbZRS6h6ig+ONDiK/DhNXukbSEcRQgHdJecEOVg0CDlp4aI02PPHorOKnJwzzlYVrndal2U8bPaZuXtOTXMC54nfQ+xdchY8xrKhA+mVJsn2ZYY66+G4CzhjWSsSJa82nN1UeEIWHMWHAc0nrL9z+JMGITPawp4ZSrfciv1W9heSUt0cYKrZiKQUuOIvZesJ4x56UAJTBaA2iFiJ1Svpe91LELsbOu7xBkZ8uEWPxWbRaPDgClclR2UlGlhaVeR0m9dRBCFCYw945j9TwWXYiOKQRJPfZAleeBeiUlBamDFaa3VvQ/ILmZhbNSHvB8PdatAwPgEAynQnFNlfecOq8t7azrfGcc6uhJTZ4wTTj+AOrDENO8zH2ubl6r3qH7uE7lwak2330dJvrPtqPHOX7BVJUdh2Eq93Vb66UmRwzLmvC4vVftG9mX46oO27MleaO0q8lLDimvp1tUiUZLpi6Xy+VfAAAA//8DAHpkYp/zBAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTS2/cNhD+KwLPosw3Rd2MdYou3MSLZJEWvfExignL1IKinATG/veA2g2c9NYLQQz4zXyP4StK9hnQgG5Pp3cvkMqCWlTiVmKEKUwUJvRIycDFIGVnJDGG039Ri+I9fEcD0sJ76g1gDgqwCERix0ePQwBDCQ2ESVF72i8LGl6RjV2Al+ihm5fPkJc4JzSgv2MK89c628bOT/MaujxPsE9LsclXMh/2u3v81+3h+HC4vFpgqeAuBjSgvleC9JRiLmnAQmqHjR4d9j3XShnHPWMXWEwFcrJTt4Snt/lhLgmKH1jHeEcwM+jcomCLrZSdXeD4/VRZbBbd1Xq7lbfr8IpeIKOBtT/NvLS78VO8eV7cGqdwczmLXZ6qylOeT5BLhM2TTeY02RLn1Fwn7aYIqaAWPXxqDpMt45yffzPq00X//n/IP9bp776BXwuE3bymggZEeY9a9N76x5ig2d+hAREKLPAe8MiowqIPFlvJDVaKqxGoN5pR1KLdnEpM67wuzT4V+JI3AWhAf9hpgQv12+wfYwFf1lxV/aPqLhzyHFZfmjf/KelIxwmpoLWc1tJ8hBAz+AJV3zGv137/gTDFNtBuzRlSaQ62PDZ/2uURDUgqzaQgwlvJKTA/GkZGQnrDWRAMvFYBNGPeCWFGPnLpnfVO96ODwJ2uCqHG3Wwbprx1wjqFvegdFk5T7HqisCCSUzdqzmQl8uZj8zBVnPU9ERA4NjAGLAy12AjOceDSaqZGrdn4iyXX9Cmp4wO8XP7KhUEIPQRFMRNUYmEMxb2RGjvdO+NUD9Ib1KJ7yAmmX4x6H32el3kszXV5mt+s+7im+t+bbZG+xoS/bRndzf4JclMztjHV9b7Gej6fzz8AAAD//wMAV+GhYTYEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUTW/bOBT8KwLPosIvUaJuWWeLNdI2buNtF3ujyCeHiEIaFJVuEPi/Lyg7iN1iD3sTqDdv3psZ8hV5/QSoQ9f7/e/P4NOESpTccsQIk5hITOiWko6Lrq4rWgvVCPU3KpG7hRfUoUYYQ40CzEECFpbUuOeDwdaCooRawmqRe+rdhLpXpF1l4dkZqML0DeLkgkcd+u68DT8yt3aVGcNsqxhGWPspaW/yMJ/Xq1v88XqzvdscqyaYMrhyFnWobaUgLaWY19RiUTc9Vs3QY9PyRkrVc8PYEeZ8guj1WE328Z3fhuQhmY5VjFcEM4UOJbI66TxyryfYvuzzFItEN/m8XI6Xz+4VPUNEHSvfxDy2uzKju3qa+tmN9moMu53zu1Xwg9vNUadMXKJ9DHuIycEizm/O6/jyMex2uR/6oMcJUIm2EJ+c1+Pxxw0Meh7TfZjjosxkH/+rBnVIzymgEi1KjuNCW5yWWY0OfPoFe0Z8d19sRp2GEJ8uTLo/ar/+H9J/0ubBeSjWN6hDhAKzvAU8MCqxaK3GuuYKS8nlANSohlFUolXwyfk5zFOx9gl2J9Uu5ruO5sElMGmOeae/ZA7bJgY7m1S8G0xJRSpOSAbNaT+n4itYF8EkyEts43zq9xOESbaAPrgRfpFnNccIPhUbnR6KP/T0gDpUy4bVggija06BmUExMhDSKs6sYGAaaaFhzPRCqIEPvDa9Nn3TDj1Y3jd5bcghK5ZcKwKDaIXFfc0sFoPUuK2BY9X3TAxWKt2YC3GLuzHjtGmJAMuxgsFioajGSnCOLa91w+TQNGw402l7DAQlmf48hH9OYE9Z+nyM9tvuFp6P93iZU1rbgpUUM0FrLJSiuFV1g/um7VUvW6iNOho6hTchv0Hsw+RSfkS+zA5yEi8K3n25hehhPPPmkzMxTGFIxSmUxYVbX2ef37BiCegP5/E/SyxugnmEWORYaefPrDwcDod/AQAA//8DAM8mzccKBQAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeL3pzbQddpI6N7KIt+sbLyGYtUwuKchIY/vdCWjtXFOiLIAw5cw7PnJknlN0DoB6dHY+Xj5DrjFpU0xZihClMFCb0QEnPRS9lZ6WgjKu/UYvSO/iMeqRFCDRYwBwUYBGJxJ4PAccIlhIaCZNireluZ9Q/IZe6CI8pQDfNf0CZ05RRj/5MOU4fV2yXujBOS+zKNMIuz9XlsJJ5vzt/h38/uzlc35xuzTCvyV2KqEfGKEEMpZhLGrGQ2mOrB4+D4Vop63lg7JSWcoWS3djN8f4rfpxqhhp61jHeEcwsem5RdNWtlL2b4fD5uLLYJLpY4+0W3n77J/QIBfWsfRXzVO5NGNObh9kvaYxvti9q0bFMRyg1wabG9b75SoKSjnRMMUJQi65cuEsZmt1Fcz2uT3TBEAGRYwtDxMJSh63gHEcunWZq0JoNqEXvoGQYv6l6lUKZ5mmozYvIzXc4+7Pzy+z8CCvGWzfOgFq0m/fV5ejGKcPlJwhLPdU6lGU9Pp9yTXmZlrnZ5Qq3xb2cv+afL6VArs2Nq3fNb26+Qz2SSjMpiAhOcgosDJaRgRBjOYuCQdAqgmYseCHswAcug3fBazN4iNxrilr066rhfgkB5vkrm80k47hxaF76dD4myPU7GVGPCAUWuQE8MKqwMNFhJ7nFSnE1AA1Wsy8wB1duoaIeDcZ7T0LwTFgaHLOOx4HwqAbLmNZODFxqwZSSRLsQtWZURK4hOD5wYlbb3ZQpLqH+2Gq+NeCmTP9AqKtUqEeKSGmC854pQY2zBgRVwigL2orBEcGDd1Qa5z0lTpJgDQuOKuuEj3Kwr/Qv823KcJHm4+g+f4NrOtXxX4Jzhg5O/HD9bXEP8HEq9+9PNu7eXx5Qiz4sed0IzW61yMeU8Se1TvTVMtZ0uCvgIsSrKcLPPoJ1XJptQgX1joHUONigsVDUY++9w4MdgjCGaEk1atHFFO6hNKvDXMrrVH0pdr1vzkq4SxVCXcrK76+Nx/W+uRldHaby8N0m+eZhPwrQMU7J2pkIj6dttHFUMRqIimImqMTCWoqNlRp7bbz1yoAMq2LXSz0utfkAMRUIdXvxixc3zPM7CPc/i/HqgheLUrJ6bX/aYpu0/2uJPbfoAdy8FHjYtnX/hHY5Q9mQL5bTKO7yVRrHNEOYcpxRbyjpKJHiheB/XuOsE5qy5+fn538BAAD//wMAY5toPBkGAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUTW/cNhD9KwLPosxviboZ6wRduKkXySIteuPHKCYsUwuScmIY+98LardIfCp6IYgh38ybN498Q9E8AxrR7en04QViyahFJWwhRpjCRGFCj5SMXIxSdloMjA7kb9SicA+vaES9cI46DZiDAiw8kdjyyWHvQVNCPWFS1JzmW0bjGzKh8/ASHHRL/gophyWiEf0Zol++19omdG5eVt+lZYZ9zMVEV8n8sd/d499vD8eHw+VWhlzBXfBoRMOgBBkoxVxSj4XsLdb9ZLEbeK+UttwxdoGFWCBFM3fZP/2s75cSobiRdYx3BDONzi3ypphK2ZoMx9dTZbFJdFfj7RbetuMbeoGERtb+K+Yl3Y2bw81ztmuY/c1lLSY/5W2djCtLekUtOqXlBKkE2PS5zRme7fx6NPnp4+VO3eYPP8CtBfxuWWNBI6JcohZtAs2zKWGJzZXjbg4Qy3ZYUog5uP/KxVGLHr40h9mUaUnP7+bx5SLz/n+o/Mm4xxCh2d+hEREKzPMB8MSowmLwBhvJNVaKqwmo0z2jqEW7JZYQ12XNzT4W+Ja2ltCIPpo5w4XfbXKPoYAra6p9/qWqrw5p8asrzc9ZUtKRjhNSQWs5raX5DD4kcAVqE8e0XvN9fQ9him2g3ZoSxNIcTHlsfjP5EY1Iqp5JQYQzklNgbtKMTIQMmjMvGLheeegZc1YIPfGJS2eNs/0wWfDc9rVDqNZpNrdao+XAnMYwTYAFEIEHazS2Wg5eSQqyd+90bB7mijNuIAI8xxomj4WmBmvBOfZcmp6pqe/Z9IskVz9QUst7eLm8u42B8n4AryhmgkostKZ40LLHth+stmoA6TRq0T2kCPMvQn0KLi15mUpzdUjzTrrPa6x/R7O55XuI+Mc2o7vFPUFq6oxNiPWpXMd6Pp/P/wAAAP//AwAaq/UgggQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUyW7jOBD9FYFnU+GijQLmYDjdGCOdTpC4ZwZz41KMiciiQVFOB0H+fUBZjp1GH+YmlGp59d4rvqFe7gC1aLnffzlAHwe0QNFNIUZYhUmFCd1Q0vKiLcucV7RpBP0XLZC7gVfUorrQmmoBmEMFuDCkxIpbjY0BQQk1hJVF6imfBtS+IelyAwenIffDXxAG53vUor9db/xLmi1drjs/mjz4Dtb9EGWvE5jv69UN/ra839zdH7MGGFJx7gxqUdNUBWkoxbykBhdlrbCorcK64XVVCcU1Y8cy10cIvezywTyf5xsfe4i6ZTnjOcFMoPcFMjLKBFnJATav+4Rioug6xRdTePps39ABAmrZ4kTmsd2V7tzVblCj68xVlOEJog1yBy8+PMNBdmiB9sHvIUQHEzd3j9kZEiU5yVnFCEELdCv11vWQra+zuy4tLHVDCjAcC7AGF4JKLArOseGlrFll65pZtEA3EHroLrreOh384G3MZsqzT3NWvo+uH/04ZOs+wlOQ8Vj3VXYDoAW6H1Xnhu0DSPO68Q9j+tePXdplNYYAfczuZdxmf8phi1pUVjUrC1JoWXIKTFvBiCWkEZyZgoGuKwM1Y1oVhbDc8lIrqVXdWAWGq5qiBbob436MJ/5/XoDYBLfbgTkj2Ewc33cyWh92awN9dNYlaU4Zk6G6btoqm3uuOgd9/EQyahGhwAxvAFtGK1w0RmJZcoGrilcWqBY1S+hWvrfuafzgiVhrCm7LpqmUUJQAs3VTkNoqYrTlWphSQc1qUjfQCE2IKgvaKKGJAVoWJm0XvBl1/NUKfBLoYezTdWbrtPaL6/HPqjgzch989Np3STDXwePrEGH3O9VWfrf3g4tpfztL+2OAW9+nv/P9z5zNxUsfzzFId5BNpweyVpYyiUlDLS5UbbCsuMK8IrVoqpoJlZx47fUzhCwZTLp+0uTkqbvHbBn01kXQcQxp8j/TUkc5v55O5sxH/v3LZuUDLPf7xRz945BIOjY7GeDTu/JjSOlbP6QtYhgvfPQInT3hunCTgcPxpXIpWBnTgKkoZgUtcSEExY0oa6zqRglVNVBq8eHW7AGMC6Dj1G9znDYT+ztbLkN0Vuo4pNv55vXkptmec8qvIC9U+6g+Dr9QbnbS3ImS5NjH47s5Geh/PZvv7+/v/wEAAP//AwDMb2aaJwYAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUy27cNhSGX0XgWlR4FUntDLtFB27qQWK0RTcDijz0sJYplaKcBsa8eyDNGI6bVXcScf5z+fgfvqBknwB16GqafnqGVGZUoxK3I0ZYi0mLCb2npOOik20jNOfc6L9QjeItfEUdUsI56gxgDi1g4YnEPQ8Oew+GEuoJk2LNaR9m1L0gGxsPz9FBM86/Q57jmFCH/ojJj1/W2jY2bhgX3+RxgF2ai01ubea33fUt/vVqf3+3P0fNMK/iJnrUIa1bQTSlmEvqsZCqx0aFHjvNVduanjvGzrKYCuRkh2b2j2/1/VgSFNexhvGGYGbQqUbeFru23NsZ7r9Oaxcbopv1vN6Ot8/uBT1DRh2rX2Ge031wQ/yQl4RqNOVxglwibAy2sYbBljim6pL5eoiQyjn0b3DlsM3FtFPG9waEUM6DBcm9I54JwkH7ngYqhONgtCNGUElcIJrZEJjUNugeUI3uPlf7wZYw5qd3oD+f+e3+B76P1h1jgmp3gzpEKDDPNeDAaIuF9hZbyQ1uW94GoM4oRlGNrsdUYlrGZa52qcBD3qZGHfrZDvOlv6vsjrGAK0teUfzZroYZ7JLc8TDlMcQBDnE+eAh2GQrqULho93n0iyvV20VS0pCGE7ImXsq0lOoT+JjBFVgHvc/LpeZ/JKxlm+h6yRlSqfa2HKtf7HxEHZKtYlIQ4azkFJgLhpFAiDacecHAqdaDYsz1QpjAA5eut65XOvTgea9WCrD6ptquVFrbu+AVFhIIFlz22EoqMKNWGcW4IRzesa7uhlVnnSYCPMcGgsfCUIuN4Bx7Lq1ibVCKhdXi03QoZ0u9Omk1qv+O1sVxlNAfMWf4Z4H5DOvC+7Cc5R6ez5u7jdF6r8G3FDNBJRbGUKyNVLhXujd9q0E6g2p0CznB8B3tj9HlcR5DqS5WrN7x/7Sk9fWpNlt+iQn/u5nhZnSPkKvVTDamddku/jnV6AnsvGR42h6v7gVN1j3ah3WUABmSg4Mbl1S2/Zz94+sffVu1HyLJ6XQ6fQMAAP//AwB7Idn4GwUAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTXW/UMBD8K5Gf49RfseO8VVcQpwI90QoQb4694SxS++Q4pVV1/x05d6gtb7xZK8/s7MzuMwrmHlCPLg+Hdw8Q8oxqlP1aYoRJTCQm9I6Snrc9UY0gTDIqf6Aa+Wt4Qj1SwlpqNWAOErBwpMUDHy12DjQl1BHWisJpfs6of0bGNw4evIUmzl8hzT4G1KNvPrj4u/Q2vrFTXFyT4gTbMGcTbBHzebu5xh8vd3c3u9OvGeYCbrxDPeo6KUhHKeYtdVi0asBajQO2HVdS6oFbxk4wHzKkYKZmdr9e+ruYA2Tbs4bxhmCm0bFGzmRTJA9mhrunQ1GxWnRV6vVaXp/9M3qAhHpW/zXzRHdhJ39h4/29Ce5i9MHPe1SjQ4oHSNnDasc64TSZ7GOozk02k4eQUY1ubqvdZPIY0/0bj25Po2//Y/JPxu59gGp7hXpEKDDHO8AjoxKLzhlsWq6xlFyOQK1WjKIabWLIPixxmattyPAzrSpRj96baYaTvstk9z6DzUsq0r/LkjU8+ryJrhRwIdql6Babqxe/KWlIwwkpJEs+LLn6As4nsBnKUHdpOfP/A2GSraDNkhKEXO1M3lcfzLxHPWqlYq0gwpqWU2B21IyMhHSaMycYWCUdKMbsIIQe+chbOxg7qG4cwPFBFaFQ4q3WjWKckZZriTvFHRZcO2xIS/HIneNaM6Fp98bX6mYqOGM7IsBxrGF0WGhqsBacY8dbo5gclWLjK0vOkVNS2jt4ON3GqkA614GTFDNBWyy0prjTrcKD6gY9yA5aq1GNriEFmF4Z9cnbFOc45uq8MdUb674sodx3tW7Pbx/w45rZVbS/IFUlc+NDWedzzMfj8fgHAAD//wMAG25qdSYEAAA=

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using PlotLine.Services;
namespace PlotLine.Controllers;
public sealed class StoryBibleController(IStoryBibleService storyBible) : Controller
{
public async Task<IActionResult> Index(int projectId, string? q)
{
var model = await storyBible.GetAsync(projectId, q);
return model is null ? NotFound() : View(model);
}
}

View File

@ -177,6 +177,19 @@ public interface IAnalyticsRepository
Task<AnalyticsData> GetStoryHealthAsync(int projectId, int? bookId, decimal? startChapterNumber, decimal? endChapterNumber, decimal? startSceneNumber, decimal? endSceneNumber); Task<AnalyticsData> GetStoryHealthAsync(int projectId, int? bookId, decimal? startChapterNumber, decimal? endChapterNumber, decimal? startSceneNumber, decimal? endSceneNumber);
} }
public interface IStoryBibleRepository
{
Task<IReadOnlyList<StoryBibleCharacterSummary>> GetCharacterSummariesAsync(int projectId);
Task<IReadOnlyList<StoryBibleThreadSummary>> GetThreadSummariesAsync(int projectId);
Task<IReadOnlyList<StoryBibleAssetSummary>> GetAssetSummariesAsync(int projectId);
Task<IReadOnlyList<StoryBibleRelationshipSummary>> GetRelationshipSummariesAsync(int projectId);
Task<IReadOnlyList<StoryBibleLocationSummary>> GetLocationSummariesAsync(int projectId);
Task<IReadOnlyList<StoryBibleTimelineSummary>> GetTimelineSummaryAsync(int projectId);
Task<IReadOnlyList<StoryBibleAttentionItem>> GetOpenQuestionsAsync(int projectId);
Task<IReadOnlyList<StoryBibleRevisionItem>> GetRevisionProgressAsync(int projectId);
Task<IReadOnlyList<StoryBibleSearchResult>> SearchAsync(int projectId, string? query);
}
public sealed class TimelineData public sealed class TimelineData
{ {
public Project? Project { get; init; } public Project? Project { get; init; }
@ -491,6 +504,63 @@ public sealed class AnalyticsRepository(ISqlConnectionFactory connectionFactory)
} }
} }
public sealed class StoryBibleRepository(ISqlConnectionFactory connectionFactory) : IStoryBibleRepository
{
public async Task<IReadOnlyList<StoryBibleCharacterSummary>> GetCharacterSummariesAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleCharacterSummary>("dbo.StoryBible_CharacterSummaries", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleThreadSummary>> GetThreadSummariesAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleThreadSummary>("dbo.StoryBible_ThreadSummaries", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleAssetSummary>> GetAssetSummariesAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleAssetSummary>("dbo.StoryBible_AssetSummaries", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleRelationshipSummary>> GetRelationshipSummariesAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleRelationshipSummary>("dbo.StoryBible_RelationshipSummaries", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleLocationSummary>> GetLocationSummariesAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleLocationSummary>("dbo.StoryBible_LocationSummaries", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleTimelineSummary>> GetTimelineSummaryAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleTimelineSummary>("dbo.StoryBible_TimelineSummary", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleAttentionItem>> GetOpenQuestionsAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleAttentionItem>("dbo.StoryBible_OpenQuestions", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleRevisionItem>> GetRevisionProgressAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleRevisionItem>("dbo.StoryBible_RevisionProgress", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure)).ToList();
}
public async Task<IReadOnlyList<StoryBibleSearchResult>> SearchAsync(int projectId, string? query)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryBibleSearchResult>("dbo.StoryBible_Search", new { ProjectID = projectId, Query = query }, commandType: CommandType.StoredProcedure)).ToList();
}
}
public sealed class SceneDependencyRepository(ISqlConnectionFactory connectionFactory) : ISceneDependencyRepository public sealed class SceneDependencyRepository(ISqlConnectionFactory connectionFactory) : ISceneDependencyRepository
{ {
public async Task<SceneDependencyLookupData> GetLookupsAsync() public async Task<SceneDependencyLookupData> GetLookupsAsync()

View File

@ -1014,6 +1014,139 @@ public sealed class AnalyticsCharacterAppearance
public int SceneCount { get; set; } public int SceneCount { get; set; }
} }
public sealed class StoryBibleCharacterSummary
{
public int CharacterID { get; set; }
public string CharacterName { get; set; } = string.Empty;
public string? ShortName { get; set; }
public string? Description { get; set; }
public string? EyeColour { get; set; }
public string? Height { get; set; }
public int? AgeAtSeriesStart { get; set; }
public int SceneCount { get; set; }
public int PovSceneCount { get; set; }
public int KnowledgeCount { get; set; }
public int RelationshipCount { get; set; }
public int MissingLocationCount { get; set; }
public int? FirstSceneID { get; set; }
public string? FirstSceneLabel { get; set; }
public int? LastSceneID { get; set; }
public string? LastSceneLabel { get; set; }
public string? LocationHistory { get; set; }
}
public sealed class StoryBibleThreadSummary
{
public int PlotThreadID { get; set; }
public string ThreadTitle { get; set; } = string.Empty;
public string PlotLineName { get; set; } = string.Empty;
public string ThreadTypeName { get; set; } = string.Empty;
public string ThreadStatusName { get; set; } = string.Empty;
public int Importance { get; set; }
public int EventCount { get; set; }
public int? IntroducedSceneID { get; set; }
public string? IntroducedSceneLabel { get; set; }
public int? LastTouchedSceneID { get; set; }
public string? LastTouchedSceneLabel { get; set; }
public int? PlannedResolutionSceneID { get; set; }
public string? PlannedResolutionSceneLabel { get; set; }
public int? ActualResolutionSceneID { get; set; }
public string? ActualResolutionSceneLabel { get; set; }
public string? EventHistory { get; set; }
public int WarningCount { get; set; }
}
public sealed class StoryBibleAssetSummary
{
public int StoryAssetID { get; set; }
public string AssetName { get; set; } = string.Empty;
public string KindName { get; set; } = string.Empty;
public int Importance { get; set; }
public string? CurrentStateName { get; set; }
public string? CurrentLocationPath { get; set; }
public bool IsResolved { get; set; }
public int EventCount { get; set; }
public int DependencyCount { get; set; }
public int CustodyCount { get; set; }
public int SceneCount { get; set; }
public string? EventHistory { get; set; }
public string? DependencySummary { get; set; }
public string? CustodySummary { get; set; }
public int WarningCount { get; set; }
}
public sealed class StoryBibleRelationshipSummary
{
public int CharacterRelationshipID { get; set; }
public int CharacterAID { get; set; }
public string CharacterAName { get; set; } = string.Empty;
public int CharacterBID { get; set; }
public string CharacterBName { get; set; } = string.Empty;
public string RelationshipTypeName { get; set; } = string.Empty;
public string? CurrentStateName { get; set; }
public int EventCount { get; set; }
public string? FirstEventLabel { get; set; }
public string? EventHistory { get; set; }
}
public sealed class StoryBibleLocationSummary
{
public int LocationID { get; set; }
public string LocationName { get; set; } = string.Empty;
public string LocationPath { get; set; } = string.Empty;
public string? LocationTypeName { get; set; }
public int SceneCount { get; set; }
public int CharacterCount { get; set; }
public int AssetCount { get; set; }
public int RelationshipCount { get; set; }
public string? RelatedLocations { get; set; }
}
public sealed class StoryBibleTimelineSummary
{
public int BookID { get; set; }
public string BookTitle { get; set; } = string.Empty;
public int ChapterID { get; set; }
public decimal ChapterNumber { get; set; }
public string ChapterTitle { get; set; } = string.Empty;
public int SceneID { get; set; }
public decimal SceneNumber { get; set; }
public string SceneTitle { get; set; } = string.Empty;
public string? Summary { get; set; }
public string? PovCharacterName { get; set; }
public string? LocationPath { get; set; }
public string RevisionStatusName { get; set; } = string.Empty;
}
public sealed class StoryBibleAttentionItem
{
public string Category { get; set; } = string.Empty;
public string SeverityName { get; set; } = "Info";
public string Title { get; set; } = string.Empty;
public string? Detail { get; set; }
public int? SceneID { get; set; }
public int? EntityID { get; set; }
public string? EntityType { get; set; }
}
public sealed class StoryBibleRevisionItem
{
public string GroupName { get; set; } = string.Empty;
public int ItemCount { get; set; }
public string? Detail { get; set; }
public int? SceneID { get; set; }
public int? ChapterID { get; set; }
}
public sealed class StoryBibleSearchResult
{
public string ResultType { get; set; } = string.Empty;
public int EntityID { get; set; }
public string Title { get; set; } = string.Empty;
public string? Detail { get; set; }
public int? SceneID { get; set; }
}
public sealed class AnalyticsBusyScene public sealed class AnalyticsBusyScene
{ {
public int SceneID { get; set; } public int SceneID { get; set; }

View File

@ -32,6 +32,7 @@ public class Program
builder.Services.AddScoped<IWarningRepository, WarningRepository>(); builder.Services.AddScoped<IWarningRepository, WarningRepository>();
builder.Services.AddScoped<ISceneDependencyRepository, SceneDependencyRepository>(); builder.Services.AddScoped<ISceneDependencyRepository, SceneDependencyRepository>();
builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>(); builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>();
builder.Services.AddScoped<IStoryBibleRepository, StoryBibleRepository>();
builder.Services.AddScoped<IProjectService, ProjectService>(); builder.Services.AddScoped<IProjectService, ProjectService>();
builder.Services.AddScoped<IBookService, BookService>(); builder.Services.AddScoped<IBookService, BookService>();
builder.Services.AddScoped<IChapterService, ChapterService>(); builder.Services.AddScoped<IChapterService, ChapterService>();
@ -43,6 +44,7 @@ public class Program
builder.Services.AddScoped<ILocationService, LocationService>(); builder.Services.AddScoped<ILocationService, LocationService>();
builder.Services.AddScoped<IContinuityValidationService, ContinuityValidationService>(); builder.Services.AddScoped<IContinuityValidationService, ContinuityValidationService>();
builder.Services.AddScoped<IAnalyticsService, AnalyticsService>(); builder.Services.AddScoped<IAnalyticsService, AnalyticsService>();
builder.Services.AddScoped<IStoryBibleService, StoryBibleService>();
var app = builder.Build(); var app = builder.Build();

View File

@ -145,6 +145,11 @@ public interface IAnalyticsService
Task<AnalyticsViewModel?> GetStoryHealthAsync(int projectId, int? bookId, decimal? startChapterNumber, decimal? endChapterNumber, decimal? startSceneNumber, decimal? endSceneNumber); Task<AnalyticsViewModel?> GetStoryHealthAsync(int projectId, int? bookId, decimal? startChapterNumber, decimal? endChapterNumber, decimal? startSceneNumber, decimal? endSceneNumber);
} }
public interface IStoryBibleService
{
Task<StoryBibleViewModel?> GetAsync(int projectId, string? query);
}
public sealed class ProjectService(IProjectRepository projects, IBookRepository books) : IProjectService public sealed class ProjectService(IProjectRepository projects, IBookRepository books) : IProjectService
{ {
public Task<IReadOnlyList<Project>> ListAsync() => projects.ListAsync(); public Task<IReadOnlyList<Project>> ListAsync() => projects.ListAsync();
@ -1694,6 +1699,33 @@ public sealed class AnalyticsService(IProjectRepository projects, IBookRepositor
} }
} }
public sealed class StoryBibleService(IProjectRepository projects, IStoryBibleRepository storyBible) : IStoryBibleService
{
public async Task<StoryBibleViewModel?> GetAsync(int projectId, string? query)
{
var project = await projects.GetAsync(projectId);
if (project is null)
{
return null;
}
return new StoryBibleViewModel
{
Project = project,
Query = query,
Characters = await storyBible.GetCharacterSummariesAsync(projectId),
PlotThreads = await storyBible.GetThreadSummariesAsync(projectId),
Assets = await storyBible.GetAssetSummariesAsync(projectId),
Relationships = await storyBible.GetRelationshipSummariesAsync(projectId),
Locations = await storyBible.GetLocationSummariesAsync(projectId),
Timeline = await storyBible.GetTimelineSummaryAsync(projectId),
OpenQuestions = await storyBible.GetOpenQuestionsAsync(projectId),
RevisionItems = await storyBible.GetRevisionProgressAsync(projectId),
SearchResults = await storyBible.SearchAsync(projectId, query)
};
}
}
public sealed class AssetService(IProjectRepository projects, IAssetRepository assets, ILocationRepository locations) : IAssetService public sealed class AssetService(IProjectRepository projects, IAssetRepository assets, ILocationRepository locations) : IAssetService
{ {
public async Task<StoryAssetListViewModel?> GetAssetsAsync(int projectId) public async Task<StoryAssetListViewModel?> GetAssetsAsync(int projectId)

View File

@ -0,0 +1,555 @@
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_CharacterSummaries
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT ch.CharacterID,
ch.CharacterName,
ch.ShortName,
ch.DefaultDescription AS Description,
ch.EyeColour,
ch.Height,
ch.AgeAtSeriesStart,
ISNULL(sceneCounts.SceneCount, 0) AS SceneCount,
ISNULL(povCounts.PovSceneCount, 0) AS PovSceneCount,
ISNULL(knowledge.KnowledgeCount, 0) AS KnowledgeCount,
ISNULL(relationships.RelationshipCount, 0) AS RelationshipCount,
ISNULL(sceneCounts.MissingLocationCount, 0) AS MissingLocationCount,
firstScene.SceneID AS FirstSceneID,
firstScene.SceneLabel AS FirstSceneLabel,
lastScene.SceneID AS LastSceneID,
lastScene.SceneLabel AS LastSceneLabel,
locations.LocationHistory
FROM dbo.Characters ch
OUTER APPLY
(
SELECT COUNT(DISTINCT sc.SceneID) AS SceneCount,
COUNT(DISTINCT CASE WHEN sc.LocationID IS NULL THEN sc.SceneCharacterID END) AS MissingLocationCount
FROM dbo.SceneCharacters sc
INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE b.ProjectID = @ProjectID AND sc.CharacterID = ch.CharacterID
) sceneCounts
OUTER APPLY
(
SELECT COUNT(DISTINCT s.SceneID) AS PovSceneCount
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE b.ProjectID = @ProjectID AND s.IsArchived = 0 AND COALESCE(s.POVCharacterID, c.POVCharacterID) = ch.CharacterID
) povCounts
OUTER APPLY
(
SELECT COUNT(*) AS KnowledgeCount
FROM dbo.CharacterKnowledge ck
INNER JOIN dbo.Scenes s ON s.SceneID = ck.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE b.ProjectID = @ProjectID AND ck.CharacterID = ch.CharacterID
) knowledge
OUTER APPLY
(
SELECT COUNT(*) AS RelationshipCount
FROM dbo.CharacterRelationships cr
WHERE cr.ProjectID = @ProjectID AND cr.IsArchived = 0
AND (cr.CharacterAID = ch.CharacterID OR cr.CharacterBID = ch.CharacterID)
) relationships
OUTER APPLY
(
SELECT TOP (1) s.SceneID,
CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.SceneCharacters sc
INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE b.ProjectID = @ProjectID AND sc.CharacterID = ch.CharacterID
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneID
) firstScene
OUTER APPLY
(
SELECT TOP (1) s.SceneID,
CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.SceneCharacters sc
INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE b.ProjectID = @ProjectID AND sc.CharacterID = ch.CharacterID
ORDER BY b.SortOrder DESC, c.SortOrder DESC, s.SortOrder DESC, s.SceneID DESC
) lastScene
OUTER APPLY
(
SELECT STRING_AGG(CONVERT(nvarchar(max), location.LocationPath), N', ') AS LocationHistory
FROM
(
SELECT DISTINCT lp.LocationPath
FROM dbo.SceneCharacters sc
INNER JOIN dbo.LocationPaths lp ON lp.LocationID = sc.LocationID
WHERE sc.CharacterID = ch.CharacterID AND lp.ProjectID = @ProjectID
) location
) locations
WHERE ch.ProjectID = @ProjectID AND ch.IsArchived = 0
ORDER BY ch.CharacterName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_ThreadSummaries
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT pt.PlotThreadID,
pt.ThreadTitle,
pl.PlotLineName,
tt.TypeName AS ThreadTypeName,
ts.StatusName AS ThreadStatusName,
pt.Importance,
ISNULL(events.EventCount, 0) AS EventCount,
introduced.SceneID AS IntroducedSceneID,
introduced.SceneLabel AS IntroducedSceneLabel,
lastTouched.SceneID AS LastTouchedSceneID,
lastTouched.SceneLabel AS LastTouchedSceneLabel,
planned.SceneID AS PlannedResolutionSceneID,
planned.SceneLabel AS PlannedResolutionSceneLabel,
actual.SceneID AS ActualResolutionSceneID,
actual.SceneLabel AS ActualResolutionSceneLabel,
events.EventHistory,
ISNULL(warnings.WarningCount, 0) AS WarningCount
FROM dbo.PlotThreads pt
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.IsArchived = 0
INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
OUTER APPLY
(
SELECT s.SceneID,
CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
INNER JOIN dbo.Books b ON b.BookID = c.BookID
WHERE s.SceneID = pt.IntroducedSceneID
) introduced
OUTER APPLY
(
SELECT s.SceneID,
CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
INNER JOIN dbo.Books b ON b.BookID = c.BookID
WHERE s.SceneID = pt.PlannedResolutionSceneID
) planned
OUTER APPLY
(
SELECT s.SceneID,
CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
INNER JOIN dbo.Books b ON b.BookID = c.BookID
WHERE s.SceneID = pt.ActualResolutionSceneID
) actual
OUTER APPLY
(
SELECT COUNT(*) AS EventCount,
STRING_AGG(CONVERT(nvarchar(max), CONCAT(tet.TypeName, N': ', te.EventTitle)), N' -> ') WITHIN GROUP (ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, te.ThreadEventID) AS EventHistory
FROM dbo.ThreadEvents te
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
INNER JOIN dbo.Scenes s ON s.SceneID = te.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE te.PlotThreadID = pt.PlotThreadID
) events
OUTER APPLY
(
SELECT TOP (1) s.SceneID,
CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.ThreadEvents te
INNER JOIN dbo.Scenes s ON s.SceneID = te.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE te.PlotThreadID = pt.PlotThreadID
ORDER BY b.SortOrder DESC, c.SortOrder DESC, s.SortOrder DESC, te.ThreadEventID DESC
) lastTouched
OUTER APPLY
(
SELECT COUNT(*) AS WarningCount
FROM dbo.ContinuityWarnings cw
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0
AND cw.EntityID = pt.PlotThreadID AND cw.EntityType IN (N'PlotThread', N'Thread')
) warnings
WHERE pl.ProjectID = @ProjectID AND pt.IsArchived = 0
ORDER BY pl.SortOrder, pt.Importance DESC, pt.ThreadTitle;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_AssetSummaries
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT sa.StoryAssetID,
sa.AssetName,
ak.KindName,
sa.Importance,
ast.StateName AS CurrentStateName,
currentLocation.LocationPath AS CurrentLocationPath,
sa.IsResolved,
ISNULL(events.EventCount, 0) AS EventCount,
ISNULL(dependencies.DependencyCount, 0) AS DependencyCount,
ISNULL(custody.CustodyCount, 0) AS CustodyCount,
ISNULL(sceneLocations.SceneCount, 0) AS SceneCount,
events.EventHistory,
dependencies.DependencySummary,
custody.CustodySummary,
ISNULL(warnings.WarningCount, 0) AS WarningCount
FROM dbo.StoryAssets sa
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
LEFT JOIN dbo.LocationPaths currentLocation ON currentLocation.LocationID = sa.CurrentLocationID
OUTER APPLY
(
SELECT COUNT(*) AS EventCount,
STRING_AGG(CONVERT(nvarchar(max), CONCAT(aet.TypeName, N': ', ae.EventTitle)), N' -> ') WITHIN GROUP (ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, ae.AssetEventID) AS EventHistory
FROM dbo.AssetEvents ae
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
INNER JOIN dbo.Scenes s ON s.SceneID = ae.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE ae.StoryAssetID = sa.StoryAssetID
) events
OUTER APPLY
(
SELECT COUNT(*) AS DependencyCount,
STRING_AGG(CONVERT(nvarchar(max), CONCAT(source.AssetName, N' ', adt.TypeName, N' ', target.AssetName)), N'; ') AS DependencySummary
FROM dbo.AssetDependencies ad
INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID
INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID
WHERE ad.IsArchived = 0 AND (ad.SourceAssetID = sa.StoryAssetID OR ad.TargetAssetID = sa.StoryAssetID)
) dependencies
OUTER APPLY
(
SELECT COUNT(*) AS CustodyCount,
STRING_AGG(CONVERT(nvarchar(max), CONCAT(acet.TypeName, N': ', ISNULL(chars.CharacterNames, N'No named holder'))), N'; ') AS CustodySummary
FROM dbo.AssetCustodyEvents ace
INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID
OUTER APPLY
(
SELECT STRING_AGG(CONVERT(nvarchar(max), COALESCE(ch.CharacterName, acec.CharacterNameText)), N', ') AS CharacterNames
FROM dbo.AssetCustodyEventCharacters acec
LEFT JOIN dbo.Characters ch ON ch.CharacterID = acec.CharacterID
WHERE acec.AssetCustodyEventID = ace.AssetCustodyEventID
) chars
WHERE ace.StoryAssetID = sa.StoryAssetID
) custody
OUTER APPLY
(
SELECT COUNT(DISTINCT SceneID) AS SceneCount
FROM
(
SELECT ae.SceneID FROM dbo.AssetEvents ae WHERE ae.StoryAssetID = sa.StoryAssetID
UNION
SELECT sal.SceneID FROM dbo.SceneAssetLocations sal WHERE sal.StoryAssetID = sa.StoryAssetID
) x
) sceneLocations
OUTER APPLY
(
SELECT COUNT(*) AS WarningCount
FROM dbo.ContinuityWarnings cw
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0
AND cw.EntityID = sa.StoryAssetID AND cw.EntityType IN (N'StoryAsset', N'Asset')
) warnings
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0
ORDER BY sa.Importance DESC, sa.AssetName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_RelationshipSummaries
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT cr.CharacterRelationshipID,
cr.CharacterAID,
ca.CharacterName AS CharacterAName,
cr.CharacterBID,
cb.CharacterName AS CharacterBName,
rt.TypeName AS RelationshipTypeName,
currentState.StateName AS CurrentStateName,
ISNULL(events.EventCount, 0) AS EventCount,
firstEvent.SceneLabel AS FirstEventLabel,
events.EventHistory
FROM dbo.CharacterRelationships cr
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
OUTER APPLY
(
SELECT TOP (1) rs.StateName
FROM dbo.RelationshipEvents re
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE re.CharacterRelationshipID = cr.CharacterRelationshipID
ORDER BY b.SortOrder DESC, c.SortOrder DESC, s.SortOrder DESC, re.RelationshipEventID DESC
) currentState
OUTER APPLY
(
SELECT COUNT(*) AS EventCount,
STRING_AGG(CONVERT(nvarchar(max), CONCAT(rs.StateName, ISNULL(N': ' + re.Description, N''))), N' -> ') WITHIN GROUP (ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, re.RelationshipEventID) AS EventHistory
FROM dbo.RelationshipEvents re
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE re.CharacterRelationshipID = cr.CharacterRelationshipID
) events
OUTER APPLY
(
SELECT TOP (1) CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
FROM dbo.RelationshipEvents re
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID AND s.IsArchived = 0
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE re.CharacterRelationshipID = cr.CharacterRelationshipID
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, re.RelationshipEventID
) firstEvent
WHERE cr.ProjectID = @ProjectID AND cr.IsArchived = 0
ORDER BY ca.CharacterName, cb.CharacterName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_LocationSummaries
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT lp.LocationID,
lp.LocationName,
lp.LocationPath,
lt.TypeName AS LocationTypeName,
ISNULL(sceneCounts.SceneCount, 0) AS SceneCount,
ISNULL(characterCounts.CharacterCount, 0) AS CharacterCount,
ISNULL(assetCounts.AssetCount, 0) AS AssetCount,
ISNULL(relationshipCounts.RelationshipCount, 0) AS RelationshipCount,
related.RelatedLocations
FROM dbo.LocationPaths lp
LEFT JOIN dbo.LocationTypes lt ON lt.LocationTypeID = lp.LocationTypeID
OUTER APPLY
(
SELECT COUNT(DISTINCT s.SceneID) AS SceneCount
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
WHERE b.ProjectID = @ProjectID AND s.IsArchived = 0 AND s.PrimaryLocationID = lp.LocationID
) sceneCounts
OUTER APPLY
(
SELECT COUNT(DISTINCT sc.CharacterID) AS CharacterCount
FROM dbo.SceneCharacters sc
WHERE sc.LocationID = lp.LocationID
) characterCounts
OUTER APPLY
(
SELECT COUNT(DISTINCT sa.StoryAssetID) AS AssetCount
FROM dbo.StoryAssets sa
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND sa.CurrentLocationID = lp.LocationID
) assetCounts
OUTER APPLY
(
SELECT COUNT(*) AS RelationshipCount
FROM dbo.LocationRelationships lr
WHERE lr.IsArchived = 0 AND (lr.FromLocationID = lp.LocationID OR lr.ToLocationID = lp.LocationID)
) relationshipCounts
OUTER APPLY
(
SELECT STRING_AGG(CONVERT(nvarchar(max), relatedLocation.LocationPath), N', ') AS RelatedLocations
FROM
(
SELECT DISTINCT other.LocationPath
FROM dbo.LocationRelationships lr
INNER JOIN dbo.LocationPaths other ON other.LocationID = CASE WHEN lr.FromLocationID = lp.LocationID THEN lr.ToLocationID ELSE lr.FromLocationID END
WHERE lr.IsArchived = 0 AND (lr.FromLocationID = lp.LocationID OR lr.ToLocationID = lp.LocationID)
) relatedLocation
) related
WHERE lp.ProjectID = @ProjectID
ORDER BY lp.LocationPath;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_TimelineSummary
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT b.BookID,
b.BookTitle,
c.ChapterID,
c.ChapterNumber,
c.ChapterTitle,
s.SceneID,
s.SceneNumber,
s.SceneTitle,
s.Summary,
pov.CharacterName AS PovCharacterName,
location.LocationPath,
rs.StatusName AS RevisionStatusName
FROM dbo.Books b
INNER JOIN dbo.Chapters c ON c.BookID = b.BookID AND c.IsArchived = 0
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
LEFT JOIN dbo.Characters pov ON pov.CharacterID = COALESCE(s.POVCharacterID, c.POVCharacterID)
LEFT JOIN dbo.LocationPaths location ON location.LocationID = s.PrimaryLocationID
WHERE b.ProjectID = @ProjectID AND b.IsArchived = 0
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, s.SceneID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_OpenQuestions
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT N'Plot Thread' AS Category, N'Info' AS SeverityName, pt.ThreadTitle AS Title,
CONCAT(N'Open thread in ', pl.PlotLineName, N' with status ', ts.StatusName) AS Detail,
CAST(NULL AS int) AS SceneID, pt.PlotThreadID AS EntityID, N'PlotThread' AS EntityType
FROM dbo.PlotThreads pt
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
WHERE pt.IsArchived = 0 AND ts.IsOpenStatus = 1
UNION ALL
SELECT N'Story Asset', N'Info', sa.AssetName,
CONCAT(N'Important unresolved asset. Importance ', sa.Importance, N'.'),
CAST(NULL AS int), sa.StoryAssetID, N'StoryAsset'
FROM dbo.StoryAssets sa
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND sa.IsResolved = 0 AND sa.Importance >= 6
UNION ALL
SELECT N'Scene', N'Warning', s.SceneTitle,
CONCAT(N'Scene ', s.SceneNumber, N' is marked Needs Work.'),
s.SceneID, s.SceneID, N'Scene'
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
WHERE s.IsArchived = 0 AND rs.StatusName = N'Needs Work'
UNION ALL
SELECT N'Scene Purpose', N'Info', s.SceneTitle,
N'No primary scene purpose has been assigned yet.',
s.SceneID, s.SceneID, N'Scene'
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0
WHERE s.IsArchived = 0 AND NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = s.SceneID)
UNION ALL
SELECT N'Scene Outcome', N'Info', s.SceneTitle,
N'Outcome notes are empty.',
s.SceneID, s.SceneID, N'Scene'
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0
WHERE s.IsArchived = 0 AND NULLIF(LTRIM(RTRIM(s.SceneOutcomeNotes)), N'') IS NULL
UNION ALL
SELECT N'Warning', ws.SeverityName, cw.Message, cw.Details,
cw.SceneID, cw.EntityID, cw.EntityType
FROM dbo.ContinuityWarnings cw
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_RevisionProgress
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT N'Scene revision' AS GroupName,
COUNT(*) AS ItemCount,
rs.StatusName AS Detail,
CAST(NULL AS int) AS SceneID,
CAST(NULL AS int) AS ChapterID
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
WHERE s.IsArchived = 0
GROUP BY rs.StatusName, rs.SortOrder
UNION ALL
SELECT N'Unfinished chapters',
COUNT(DISTINCT c.ChapterID),
N'Chapters with at least one scene not revised, polished, locked, or cut',
CAST(NULL AS int),
CAST(NULL AS int)
FROM dbo.Chapters c
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
WHERE c.IsArchived = 0 AND rs.StatusName NOT IN (N'Revised', N'Polished', N'Locked', N'Cut Candidate');
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryBible_Search
@ProjectID int,
@Query nvarchar(200) = NULL
AS
BEGIN
SET NOCOUNT ON;
IF NULLIF(LTRIM(RTRIM(@Query)), N'') IS NULL
RETURN;
DECLARE @Term nvarchar(204) = N'%' + LTRIM(RTRIM(@Query)) + N'%';
SELECT N'Character' AS ResultType, ch.CharacterID AS EntityID, ch.CharacterName AS Title,
ch.DefaultDescription AS Detail, CAST(NULL AS int) AS SceneID
FROM dbo.Characters ch
WHERE ch.ProjectID = @ProjectID AND ch.IsArchived = 0
AND (ch.CharacterName LIKE @Term OR ch.ShortName LIKE @Term OR ch.DefaultDescription LIKE @Term)
UNION ALL
SELECT N'Scene', s.SceneID, CONCAT(N'Scene ', s.SceneNumber, N': ', s.SceneTitle),
s.Summary, s.SceneID
FROM dbo.Scenes s
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0
WHERE s.IsArchived = 0 AND (s.SceneTitle LIKE @Term OR s.Summary LIKE @Term OR s.ScenePurposeNotes LIKE @Term OR s.SceneOutcomeNotes LIKE @Term)
UNION ALL
SELECT N'Plot Thread', pt.PlotThreadID, pt.ThreadTitle, pt.Summary, CAST(NULL AS int)
FROM dbo.PlotThreads pt
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0
WHERE pt.IsArchived = 0 AND (pt.ThreadTitle LIKE @Term OR pt.Summary LIKE @Term)
UNION ALL
SELECT N'Story Asset', sa.StoryAssetID, sa.AssetName, sa.Description, CAST(NULL AS int)
FROM dbo.StoryAssets sa
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0 AND (sa.AssetName LIKE @Term OR sa.Description LIKE @Term)
UNION ALL
SELECT N'Location', lp.LocationID, lp.LocationPath, lp.Description, CAST(NULL AS int)
FROM dbo.LocationPaths lp
WHERE lp.ProjectID = @ProjectID AND (lp.LocationName LIKE @Term OR lp.LocationPath LIKE @Term OR lp.Description LIKE @Term)
UNION ALL
SELECT N'Relationship', cr.CharacterRelationshipID, CONCAT(ca.CharacterName, N' - ', cb.CharacterName),
CONCAT(rt.TypeName, N'. ', ISNULL(cr.Notes, N'')), CAST(NULL AS int)
FROM dbo.CharacterRelationships cr
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
WHERE cr.ProjectID = @ProjectID AND cr.IsArchived = 0
AND (ca.CharacterName LIKE @Term OR cb.CharacterName LIKE @Term OR rt.TypeName LIKE @Term OR cr.Notes LIKE @Term)
UNION ALL
SELECT N'Warning', cw.ContinuityWarningID, cw.Message, cw.Details, cw.SceneID
FROM dbo.ContinuityWarnings cw
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0
AND (cw.Message LIKE @Term OR cw.Details LIKE @Term OR cw.EntityType LIKE @Term)
ORDER BY ResultType, Title;
END;
GO

View File

@ -229,6 +229,21 @@ public sealed class TimelineViewModel
public ContinuityValidationSummary? LastValidationSummary { get; set; } public ContinuityValidationSummary? LastValidationSummary { get; set; }
} }
public sealed class StoryBibleViewModel
{
public Project Project { get; set; } = new();
public string? Query { get; set; }
public IReadOnlyList<StoryBibleCharacterSummary> Characters { get; set; } = [];
public IReadOnlyList<StoryBibleThreadSummary> PlotThreads { get; set; } = [];
public IReadOnlyList<StoryBibleAssetSummary> Assets { get; set; } = [];
public IReadOnlyList<StoryBibleRelationshipSummary> Relationships { get; set; } = [];
public IReadOnlyList<StoryBibleLocationSummary> Locations { get; set; } = [];
public IReadOnlyList<StoryBibleTimelineSummary> Timeline { get; set; } = [];
public IReadOnlyList<StoryBibleAttentionItem> OpenQuestions { get; set; } = [];
public IReadOnlyList<StoryBibleRevisionItem> RevisionItems { get; set; } = [];
public IReadOnlyList<StoryBibleSearchResult> SearchResults { get; set; } = [];
}
public sealed class TimelineFilterViewModel public sealed class TimelineFilterViewModel
{ {
public int ProjectID { get; set; } public int ProjectID { get; set; }

View File

@ -15,6 +15,7 @@
</div> </div>
<nav class="context-nav" aria-label="Project navigation"> <nav class="context-nav" aria-label="Project navigation">
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a> <a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
<a asp-controller="StoryBible" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story Bible</a>
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a> <a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a> <a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a> <a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
@ -36,6 +37,7 @@
<div class="button-row"> <div class="button-row">
<a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Project.ProjectID">Edit project</a> <a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Project.ProjectID">Edit project</a>
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a> <a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
<a class="btn btn-outline-primary" asp-controller="StoryBible" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story Bible</a>
<a class="btn btn-outline-primary" asp-controller="PlotLines" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot lines</a> <a class="btn btn-outline-primary" asp-controller="PlotLines" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot lines</a>
<a class="btn btn-outline-primary" asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot threads</a> <a class="btn btn-outline-primary" asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Plot threads</a>
<a class="btn btn-outline-primary" asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story assets</a> <a class="btn btn-outline-primary" asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story assets</a>

View File

@ -0,0 +1,396 @@
@model StoryBibleViewModel
@{
ViewData["Title"] = $"Story Bible - {Model.Project.ProjectName}";
var timelineByBook = Model.Timeline.GroupBy(x => new { x.BookID, x.BookTitle });
var relationshipsByCharacter = Model.Relationships.GroupBy(x => new { x.CharacterAID, x.CharacterAName });
var revisionByGroup = Model.RevisionItems.GroupBy(x => x.GroupName);
}
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
<a asp-controller="Projects" asp-action="Index">Projects</a>
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
<span>Story Bible</span>
</nav>
<section class="context-header">
<div class="context-title">
<span class="eyebrow">Project workspace</span>
<strong>@Model.Project.ProjectName</strong>
</div>
<nav class="context-nav" aria-label="Project navigation">
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Timeline</a>
<a class="active" asp-controller="StoryBible" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story Bible</a>
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
</nav>
</section>
<div class="page-heading story-bible-heading">
<div>
<p class="eyebrow">Reference</p>
<h1>Story Bible</h1>
<p class="lead-text">A project-level reference for people, threads, places, assets, continuity, and revision attention.</p>
</div>
<form class="story-bible-search" asp-action="Index" method="get">
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<input class="form-control" name="q" value="@Model.Query" placeholder="Search story bible" />
<button class="btn btn-primary" type="submit">Search</button>
</form>
</div>
<nav class="story-bible-nav" aria-label="Story Bible sections">
<a href="#characters">Characters</a>
<a href="#threads">Plot Threads</a>
<a href="#assets">Story Assets</a>
<a href="#relationships">Relationships</a>
<a href="#locations">Locations</a>
<a href="#timeline-summary">Timeline Summary</a>
<a href="#attention">Things that may need attention</a>
<a href="#revision">Revision Status</a>
</nav>
@if (!string.IsNullOrWhiteSpace(Model.Query))
{
<section class="story-bible-section" id="search-results">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Search</p>
<h2>Results for "@Model.Query"</h2>
</div>
<a class="btn btn-outline-secondary btn-sm" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Clear search</a>
</div>
@if (!Model.SearchResults.Any())
{
<p class="muted">No matching story bible entries.</p>
}
else
{
<div class="story-bible-search-results">
@foreach (var group in Model.SearchResults.GroupBy(x => x.ResultType))
{
<article class="story-bible-card">
<h3>@group.Key</h3>
<ul class="story-bible-link-list">
@foreach (var result in group)
{
<li>
<a href="@StoryBibleLink(result, Model.Project.ProjectID)">@result.Title</a>
@if (!string.IsNullOrWhiteSpace(result.Detail))
{
<span>@result.Detail</span>
}
</li>
}
</ul>
</article>
}
</div>
}
</section>
}
<section class="story-bible-section" id="characters">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">People</p>
<h2>Characters</h2>
</div>
<span class="soft-count">@Model.Characters.Count characters</span>
</div>
<div class="story-bible-grid">
@foreach (var character in Model.Characters)
{
<article class="story-bible-card">
<div class="story-bible-card-title">
<h3>@character.CharacterName</h3>
<span>@character.SceneCount scenes</span>
</div>
<p>@(character.Description ?? "No profile description yet.")</p>
<div class="story-bible-meta">
@if (character.AgeAtSeriesStart.HasValue) { <span>Age @character.AgeAtSeriesStart</span> }
@if (!string.IsNullOrWhiteSpace(character.EyeColour)) { <span>@character.EyeColour eyes</span> }
@if (!string.IsNullOrWhiteSpace(character.Height)) { <span>@character.Height</span> }
<span>@character.PovSceneCount POV scenes</span>
<span>@character.RelationshipCount relationships</span>
<span>@character.KnowledgeCount knowledge notes</span>
</div>
<dl class="story-bible-facts">
<dt>First</dt><dd>@(character.FirstSceneLabel ?? "Not placed")</dd>
<dt>Last</dt><dd>@(character.LastSceneLabel ?? "Not placed")</dd>
<dt>Locations</dt><dd>@(character.LocationHistory ?? "No location history yet")</dd>
</dl>
@if (character.MissingLocationCount > 0)
{
<span class="badge text-bg-warning">@character.MissingLocationCount missing locations</span>
}
<div class="story-bible-actions">
<a asp-controller="Characters" asp-action="Details" asp-route-id="@character.CharacterID">Open profile</a>
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusCharacterId="@character.CharacterID">Show on timeline</a>
</div>
</article>
}
</div>
</section>
<section class="story-bible-section" id="threads">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Questions and promises</p>
<h2>Plot Threads</h2>
</div>
<span class="soft-count">@Model.PlotThreads.Count threads</span>
</div>
<div class="story-bible-grid">
@foreach (var thread in Model.PlotThreads)
{
<article class="story-bible-card">
<div class="story-bible-card-title">
<h3>@thread.ThreadTitle</h3>
<span>@thread.ThreadStatusName</span>
</div>
<div class="story-bible-meta">
<span>@thread.PlotLineName</span>
<span>@thread.ThreadTypeName</span>
<span>Importance @thread.Importance</span>
<span>@thread.EventCount events</span>
</div>
<dl class="story-bible-facts">
<dt>Introduced</dt><dd>@(thread.IntroducedSceneLabel ?? "Not set")</dd>
<dt>Last touched</dt><dd>@(thread.LastTouchedSceneLabel ?? "No events yet")</dd>
<dt>Planned end</dt><dd>@(thread.PlannedResolutionSceneLabel ?? "Not planned")</dd>
<dt>Actual end</dt><dd>@(thread.ActualResolutionSceneLabel ?? "Not resolved")</dd>
<dt>History</dt><dd>@(thread.EventHistory ?? "No event history yet")</dd>
</dl>
@if (thread.WarningCount > 0)
{
<span class="badge text-bg-warning">@thread.WarningCount warnings</span>
}
<div class="story-bible-actions">
<a asp-controller="PlotThreads" asp-action="Edit" asp-route-id="@thread.PlotThreadID">Edit thread</a>
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusPlotThreadId="@thread.PlotThreadID">Show on timeline</a>
</div>
</article>
}
</div>
</section>
<section class="story-bible-section" id="assets">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Objects and evidence</p>
<h2>Story Assets</h2>
</div>
<span class="soft-count">@Model.Assets.Count assets</span>
</div>
<div class="story-bible-grid">
@foreach (var asset in Model.Assets)
{
<article class="story-bible-card">
<div class="story-bible-card-title">
<h3>@asset.AssetName</h3>
<span>@(asset.IsResolved ? "Resolved" : "Open")</span>
</div>
<div class="story-bible-meta">
<span>@asset.KindName</span>
<span>@(asset.CurrentStateName ?? "No state")</span>
<span>Importance @asset.Importance</span>
<span>@asset.EventCount events</span>
</div>
<dl class="story-bible-facts">
<dt>Location</dt><dd>@(asset.CurrentLocationPath ?? "No current location")</dd>
<dt>Dependencies</dt><dd>@(asset.DependencySummary ?? "None")</dd>
<dt>Custody</dt><dd>@(asset.CustodySummary ?? "No custody history")</dd>
<dt>History</dt><dd>@(asset.EventHistory ?? "No event history yet")</dd>
</dl>
@if (asset.WarningCount > 0)
{
<span class="badge text-bg-warning">@asset.WarningCount warnings</span>
}
<div class="story-bible-actions">
<a asp-controller="StoryAssets" asp-action="Details" asp-route-id="@asset.StoryAssetID">Open asset</a>
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusAssetId="@asset.StoryAssetID">Show on timeline</a>
</div>
</article>
}
</div>
</section>
<section class="story-bible-section" id="relationships">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Character web</p>
<h2>Relationships</h2>
</div>
<span class="soft-count">@Model.Relationships.Count relationships</span>
</div>
<div class="story-bible-grid">
@foreach (var group in relationshipsByCharacter)
{
<article class="story-bible-card">
<h3>@group.Key.CharacterAName</h3>
<ul class="story-bible-link-list">
@foreach (var relationship in group)
{
<li>
<a asp-controller="Characters" asp-action="Details" asp-route-id="@relationship.CharacterBID">@relationship.CharacterBName</a>
<span>@relationship.RelationshipTypeName · @(relationship.CurrentStateName ?? "No current state")</span>
@if (!string.IsNullOrWhiteSpace(relationship.EventHistory))
{
<span>@relationship.EventHistory</span>
}
</li>
}
</ul>
</article>
}
</div>
</section>
<section class="story-bible-section" id="locations">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Place</p>
<h2>Locations</h2>
</div>
<span class="soft-count">@Model.Locations.Count locations</span>
</div>
<div class="story-bible-grid story-bible-grid-compact">
@foreach (var location in Model.Locations)
{
<article class="story-bible-card">
<div class="story-bible-card-title">
<h3>@location.LocationName</h3>
<span>@(location.LocationTypeName ?? "Location")</span>
</div>
<p>@location.LocationPath</p>
<div class="story-bible-meta">
<span>@location.SceneCount scenes</span>
<span>@location.CharacterCount characters</span>
<span>@location.AssetCount assets</span>
<span>@location.RelationshipCount links</span>
</div>
@if (!string.IsNullOrWhiteSpace(location.RelatedLocations))
{
<p class="muted">Linked to @location.RelatedLocations</p>
}
<div class="story-bible-actions">
<a asp-controller="Locations" asp-action="Details" asp-route-id="@location.LocationID">Open location</a>
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-focusLocationId="@location.LocationID">Show on timeline</a>
</div>
</article>
}
</div>
</section>
<section class="story-bible-section" id="timeline-summary">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Narrative order</p>
<h2>Timeline Summary</h2>
</div>
<a class="btn btn-outline-primary btn-sm" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Open timeline</a>
</div>
<div class="story-bible-timeline-summary">
@foreach (var book in timelineByBook)
{
<article class="story-bible-card story-bible-wide-card">
<h3>@book.Key.BookTitle</h3>
@foreach (var chapter in book.GroupBy(x => new { x.ChapterID, x.ChapterNumber, x.ChapterTitle }))
{
<div class="story-bible-chapter-summary">
<h4>Chapter @chapter.Key.ChapterNumber: @chapter.Key.ChapterTitle</h4>
@foreach (var scene in chapter)
{
<div class="story-bible-scene-summary">
<strong>Scene @scene.SceneNumber: @scene.SceneTitle</strong>
<span>@scene.RevisionStatusName</span>
<span>POV: @(scene.PovCharacterName ?? "Not set")</span>
<span>Location: @(scene.LocationPath ?? "Not set")</span>
@if (!string.IsNullOrWhiteSpace(scene.Summary))
{
<p>@scene.Summary</p>
}
</div>
}
</div>
}
</article>
}
</div>
</section>
<section class="story-bible-section" id="attention">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Open Questions</p>
<h2>Things that may need attention</h2>
</div>
<span class="soft-count">@Model.OpenQuestions.Count items</span>
</div>
<div class="attention-list">
@foreach (var item in Model.OpenQuestions)
{
<article>
<span class="badge text-bg-light">@item.Category</span>
<strong>@item.Title</strong>
@if (!string.IsNullOrWhiteSpace(item.Detail))
{
<p>@item.Detail</p>
}
@if (item.SceneID.HasValue)
{
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@item.SceneID">Open scene</a>
}
</article>
}
</div>
</section>
<section class="story-bible-section" id="revision">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Revision</p>
<h2>Revision Status</h2>
</div>
</div>
<div class="story-bible-grid story-bible-grid-compact">
@foreach (var group in revisionByGroup)
{
<article class="story-bible-card">
<div class="story-bible-card-title">
<h3>@group.Key</h3>
<span>@group.Sum(x => x.ItemCount)</span>
</div>
<ul class="story-bible-link-list">
@foreach (var item in group)
{
<li>
<span>@(item.Detail ?? item.GroupName)</span>
<strong>@item.ItemCount</strong>
</li>
}
</ul>
</article>
}
</div>
</section>
@functions {
private string StoryBibleLink(StoryBibleSearchResult result, int projectId)
{
return result.ResultType switch
{
"Scene" => Url.Action("Index", "Timeline", new { projectId, selectedSceneId = result.SceneID ?? result.EntityID }) ?? "#",
"Character" => Url.Action("Details", "Characters", new { id = result.EntityID }) ?? "#",
"Story Asset" => Url.Action("Details", "StoryAssets", new { id = result.EntityID }) ?? "#",
"Plot Thread" => Url.Action("Edit", "PlotThreads", new { id = result.EntityID }) ?? "#",
"Location" => Url.Action("Details", "Locations", new { id = result.EntityID }) ?? "#",
"Warning" => Url.Action("Index", "Warnings", new { projectId, sceneId = result.SceneID }) ?? "#",
_ => Url.Action("Index", "StoryBible", new { projectId }) ?? "#"
};
}
}

View File

@ -2352,6 +2352,248 @@ body.dragging-location [data-drag-type="location"] {
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
} }
.story-bible-heading {
align-items: end;
}
.story-bible-search {
display: flex;
gap: 8px;
min-width: min(100%, 420px);
}
.story-bible-search .form-control {
min-width: 0;
}
.story-bible-nav {
position: sticky;
top: 72px;
z-index: 4;
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0 0 18px;
padding: 10px;
border: 1px solid var(--plotline-line);
border-radius: 12px;
background: rgba(250, 249, 246, 0.92);
backdrop-filter: blur(10px);
}
.story-bible-nav a,
.story-bible-actions a {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 6px 10px;
background: rgba(47, 111, 99, 0.08);
color: var(--plotline-ink);
font-size: 0.86rem;
font-weight: 650;
text-decoration: none;
}
.story-bible-nav a:hover,
.story-bible-actions a:hover {
background: rgba(47, 111, 99, 0.16);
}
.story-bible-section {
margin: 0 0 28px;
}
.story-bible-section-heading {
display: flex;
align-items: end;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}
.story-bible-section-heading h2 {
margin: 0;
}
.soft-count {
color: var(--plotline-muted);
font-size: 0.9rem;
font-weight: 650;
white-space: nowrap;
}
.story-bible-grid,
.story-bible-search-results {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 14px;
}
.story-bible-grid-compact {
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.story-bible-card {
border: 1px solid var(--plotline-line);
border-radius: 14px;
padding: 14px;
background: rgba(255, 255, 255, 0.86);
box-shadow: 0 14px 34px rgba(36, 32, 26, 0.06);
}
.story-bible-card h3 {
margin: 0 0 8px;
font-size: 1.05rem;
}
.story-bible-card h4 {
margin: 14px 0 8px;
font-size: 0.96rem;
}
.story-bible-card p {
color: var(--plotline-muted);
}
.story-bible-card-title {
display: flex;
align-items: start;
justify-content: space-between;
gap: 10px;
}
.story-bible-card-title span {
border: 1px solid var(--plotline-line);
border-radius: 999px;
padding: 3px 8px;
background: rgba(250, 249, 246, 0.9);
color: var(--plotline-muted);
font-size: 0.78rem;
font-weight: 700;
white-space: nowrap;
}
.story-bible-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 10px 0;
}
.story-bible-meta span {
border-radius: 999px;
padding: 4px 8px;
background: rgba(36, 32, 26, 0.05);
color: var(--plotline-muted);
font-size: 0.78rem;
font-weight: 650;
}
.story-bible-facts {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px 10px;
margin: 12px 0;
}
.story-bible-facts dt {
color: var(--plotline-muted);
font-size: 0.78rem;
font-weight: 800;
text-transform: uppercase;
}
.story-bible-facts dd {
margin: 0;
min-width: 0;
overflow-wrap: anywhere;
}
.story-bible-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.story-bible-link-list {
display: grid;
gap: 8px;
margin: 0;
padding: 0;
list-style: none;
}
.story-bible-link-list li {
display: grid;
gap: 2px;
padding: 8px 0;
border-bottom: 1px solid rgba(36, 32, 26, 0.08);
}
.story-bible-link-list li:last-child {
border-bottom: 0;
}
.story-bible-link-list span {
color: var(--plotline-muted);
font-size: 0.88rem;
}
.story-bible-timeline-summary {
display: grid;
gap: 14px;
}
.story-bible-wide-card {
max-width: none;
}
.story-bible-chapter-summary {
border-top: 1px solid var(--plotline-line);
padding-top: 8px;
}
.story-bible-scene-summary {
display: grid;
grid-template-columns: minmax(220px, 1.5fr) repeat(3, minmax(120px, 0.8fr));
gap: 8px;
padding: 9px 0;
border-top: 1px solid rgba(36, 32, 26, 0.08);
}
.story-bible-scene-summary p {
grid-column: 1 / -1;
margin: 0;
}
.story-bible-scene-summary span {
color: var(--plotline-muted);
font-size: 0.88rem;
}
.attention-list {
display: grid;
gap: 10px;
}
.attention-list article {
display: grid;
grid-template-columns: max-content minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
border: 1px solid var(--plotline-line);
border-radius: 12px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.82);
}
.attention-list p {
grid-column: 2 / -1;
margin: 0;
color: var(--plotline-muted);
}
@media (max-width: 760px) { @media (max-width: 760px) {
.plotline-shell { .plotline-shell {
padding-left: 14px; padding-left: 14px;
@ -2371,6 +2613,21 @@ body.dragging-location [data-drag-type="location"] {
align-items: stretch; align-items: stretch;
flex-direction: column; flex-direction: column;
} }
.story-bible-search,
.story-bible-section-heading {
align-items: stretch;
flex-direction: column;
}
.story-bible-scene-summary,
.attention-list article {
grid-template-columns: 1fr;
}
.attention-list p {
grid-column: auto;
}
} }
@media (max-width: 1100px) { @media (max-width: 1100px) {