@marktiderman/genesis-support 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chat.ts","../src/client.ts","../src/block.ts"],"names":[],"mappings":";;;;;AASO,IAAM,qBAAA,GAAwB;AAG9B,IAAM,iBAAA,GAAoB;AAC1B,IAAM,oBAAA,GAAuB;AA8C7B,IAAM,sBAAA,GAAyB;AAAA,EACpC,IAAA,EAAM,MAAA;AAAA,EACN,SAAA,EAAW,MAAA;AAAA,EACX,WAAA,EAAa,SAAA;AAAA,EACb,SAAA,EAAW,QAAA;AAAA,EACX,QAAA,EAAU;AACZ;AAGO,SAAS,kBAAkB,KAAA,EAAwE;AACxG,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,OAAO,KAAA,CAAM,IAAA,KAAS,UAAA,IAAc,KAAA,CAAM,IAAA,KAAS,OAAA;AACrD;;;AClDO,IAAM,cAAA,GAAiB;AAGvB,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EACtC,WAAA,CACE,OAAA,EACS,IAAA,EACA,SAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAHJ,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGT,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AAAA,EACd;AAAA,EALW,IAAA;AAAA,EACA,SAAA;AAAA;AAAA,EAMX,IAAI,WAAA,GAAuB;AACzB,IAAA,OAAO,KAAK,IAAA,KAAS,OAAA;AAAA,EACvB;AACF;AAEA,SAAS,IAAA,CAAQ,WAAmB,MAAA,EAAiF;AACnH,EAAA,IAAI,MAAA,CAAO,KAAA,EAAO,MAAM,IAAI,YAAA,CAAa,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,MAAA,CAAO,KAAA,CAAM,IAAA,EAAM,SAAS,CAAA;AAC3F,EAAA,OAAO,MAAA,CAAO,IAAA;AAChB;AA2HA,IAAM,cAAA,GACJ,yKAAA;AACF,IAAM,YAAA,GAAe,yEAAA;AAKd,SAAS,SAAS,GAAA,EAA8E;AACrG,EAAA,IAAI,CAAC,GAAA,CAAI,gBAAA,EAAkB,OAAO,KAAA;AAClC,EAAA,OAAO,CAAC,GAAA,CAAI,UAAA,IAAc,IAAI,KAAK,GAAA,CAAI,gBAAgB,CAAA,CAAE,OAAA,KAAY,IAAI,IAAA,CAAK,GAAA,CAAI,UAAU,EAAE,OAAA,EAAQ;AACxG;AAEA,SAAS,SAAS,CAAA,EAAuB;AACvC,EAAA,MAAM,MAAA,GAAwC;AAAA,IAC5C,EAAA,EAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AAAA,IACf,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,KAAA,EAAQ,EAAE,KAAA,IAA2B,IAAA;AAAA,IACrC,OAAA,EAAS,MAAA,CAAO,CAAA,CAAE,OAAA,IAAW,EAAE,CAAA;AAAA,IAC/B,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,SAAA,EAAY,EAAE,UAAA,IAAgC,IAAA;AAAA,IAC9C,KAAA,EAAQ,EAAE,KAAA,IAA2B,IAAA;AAAA,IACrC,UAAA,EAAa,EAAE,WAAA,IAAiC,IAAA;AAAA,IAChD,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,UAAU,CAAA;AAAA,IAC9B,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,UAAU,CAAA;AAAA,IAC9B,gBAAA,EAAmB,EAAE,mBAAA,IAAyC,IAAA;AAAA,IAC9D,eAAA,EAAkB,EAAE,kBAAA,IAAwC,IAAA;AAAA,IAC5D,UAAA,EAAa,EAAE,YAAA,IAAkC,IAAA;AAAA,IACjD,WAAA,EAAc,EAAE,aAAA,IAAmC,IAAA;AAAA,IACnD,MAAA,EAAS,EAAE,OAAA,IAA6B;AAAA,GAC1C;AACA,EAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,EAAE;AAC/C;AAEA,SAAS,MAAA,CAAO,CAAA,EAAQ,IAAA,EAA2B,KAAA,EAAyC;AAC1F,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AAAA,IACf,KAAA,EAAO,MAAA,CAAO,CAAA,CAAE,KAAA,IAAS,EAAE,CAAA;AAAA,IAC3B,IAAA,EAAO,EAAE,IAAA,IAA0B,IAAA;AAAA,IACnC,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,SAAA,EAAY,EAAE,UAAA,IAAgC,IAAA;AAAA,IAC9C,SAAA,EAAY,EAAE,UAAA,IAAgC,IAAA;AAAA,IAC9C,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,UAAA,IAAc,CAAC,CAAA;AAAA,IACnC,OAAO,KAAA,CAAM,GAAA,CAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAC,CAAA;AAAA,IAC7B,MAAM,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAC,CAAA;AAAA,IAC3B,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,UAAU;AAAA,GAChC;AACF;AAEA,IAAM,aAAqC,EAAE,WAAA,EAAa,OAAO,YAAA,EAAc,KAAA,EAAO,cAAc,MAAA,EAAO;AAE3G,SAAS,UAAA,GAAqB;AAC5B,EAAA,MAAM,IAAK,UAAA,CAA0D,MAAA;AACrE,EAAA,IAAI,CAAA,EAAG,UAAA,EAAY,OAAO,CAAA,CAAE,UAAA,EAAW;AACvC,EAAA,MAAM,MAAM,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAG,EAAG,MAAM,IAAA,CAAK,KAAA,CAAM,KAAK,MAAA,EAAO,GAAI,EAAE,CAAA,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA;AACxF,EAAA,GAAA,CAAI,EAAE,CAAA,GAAI,GAAA;AACV,EAAA,GAAA,CAAI,EAAE,CAAA,GAAA,CAAM,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA,EAAI,EAAE,CAAA,GAAI,CAAA,GAAK,CAAA,EAAG,QAAA,CAAS,EAAE,CAAA;AACxD,EAAA,MAAM,CAAA,GAAI,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA;AACrB,EAAA,OAAO,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,KAAA,CAAM,EAAA,EAAI,EAAE,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,KAAA,CAAM,EAAA,EAAI,EAAE,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,KAAA,CAAM,EAAE,CAAC,CAAA,CAAA;AAChG;AAEA,SAAS,eAAA,GAA0B;AACjC,EAAA,MAAM,IAAK,UAAA,CAA0D,MAAA;AACrE,EAAA,IAAI,CAAA,EAAG,UAAA,EAAY,OAAO,CAAA,CAAE,UAAA,EAAW;AACvC,EAAA,OAAO,GAAG,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAC9E;AAGO,SAAS,mBAAA,CAAoB,QAAA,EAA2B,OAAA,GAAgC,EAAC,EAAkB;AAChH,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,cAAA;AACjC,EAAA,MAAM,gBAAA,GAAmB,QAAQ,gBAAA,IAAoB,IAAA;AACrD,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,eAAA;AAErC,EAAA,MAAM,KAAK,YAAoC;AAC7C,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,QAAA,CAAS,KAAK,UAAA,EAAW;AAChD,IAAA,OAAO,IAAA,CAAK,OAAA,EAAS,IAAA,EAAM,EAAA,IAAM,IAAA;AAAA,EACnC,CAAA;AAGA,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,IAAA;AAC7B,EAAA,IAAI,aAAA,GAAgC,OAAO,IAAA,GAAO,KAAA;AAClD,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAoB;AAC9C,EAAA,MAAM,OAAO,OAAA,CAAQ,iBAAA,KAAsB,CAAC,OAAA,KAAoB,OAAA,CAAQ,KAAK,OAAO,CAAA,CAAA;AAEpF,EAAA,eAAe,OAAO,QAAA,EAA8C;AAClE,IAAA,IAAI,CAAC,QAAQ,aAAA,KAAkB,KAAA,SAAc,EAAE,IAAA,EAAM,SAAA,EAAW,cAAA,EAAgB,IAAA,EAAK;AACrF,IAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,GAAA,CAAI,QAAQ,CAAA;AACxC,IAAA,IAAI,OAAO,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,gBAAgB,KAAA,EAAM;AACxD,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAA,CAAI,6BAA6B,EAAE,MAAA,EAAQ,UAAU,CAAA;AACnF,IAAA,IAAI,OAAO,KAAA,EAAO;AAChB,MAAA,IAAI,iBAAA,CAAkB,MAAA,CAAO,KAAK,CAAA,EAAG;AACnC,QAAA,aAAA,GAAgB,KAAA;AAChB,QAAA,IAAA;AAAA,UACE;AAAA,SACF;AACA,QAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,cAAA,EAAgB,IAAA,EAAK;AAAA,MACjD;AACA,MAAA,MAAM,IAAI,aAAa,MAAA,CAAO,KAAA,CAAM,SAAS,MAAA,CAAO,KAAA,CAAM,MAAM,QAAQ,CAAA;AAAA,IAC1E;AACA,IAAA,aAAA,GAAgB,IAAA;AAChB,IAAA,MAAM,EAAA,GAAK,OAAO,MAAA,CAAO,IAAA,KAAS,YAAY,MAAA,CAAO,IAAA,GAAO,OAAO,IAAA,GAAO,IAAA;AAE1E,IAAA,IAAI,CAAC,EAAA,EAAI,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,gBAAgB,IAAA,EAAK;AACxD,IAAA,aAAA,CAAc,GAAA,CAAI,UAAU,EAAE,CAAA;AAC9B,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,cAAA,EAAgB,EAAA,EAAG;AAAA,EAC5C;AAEA,EAAA,eAAe,SAAA,CAAU,QAAA,EAAkB,CAAA,EAAuB,MAAA,EAA8C;AAC9G,IAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAmB,OAAA,CAAQ,CAAC,CAAC,CAAA;AACpF,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,MAAA,IAAU,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,CAAE,KAAA,CAAM,OAAO,EAAC,CAA4B,IAAI,EAAC;AAChH,IAAA,OAAO;AAAA,MACL,IAAI,CAAA,CAAE,EAAA;AAAA,MACN,QAAA;AAAA,MACA,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,SAAA,EAAW,EAAE,UAAA,KAAe,UAAA;AAAA,MAC5B,IAAA,EAAM,OAAA,CAAQ,MAAM,CAAA,IAAK,EAAE,QAAA,KAAa,MAAA;AAAA,MACxC,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,QAAA,EAAU,EAAE,UAAA,KAAe,WAAA;AAAA,MAC3B,KAAK,CAAA,CAAE,GAAA;AAAA,MACP,WAAA,EAAa,EAAE,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,GAAA,EAAA,CAAM,EAAE,IAAA,GAAO,MAAA,CAAO,EAAE,IAAI,CAAA,GAAI,SAAS,CAAA,CAAE,GAAA,IAAO,MAAK,CAAE;AAAA,KAC1G;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAwB;AAAA,IAC5B,MAAM,OAAO,MAAA,EAAmD;AAC9D,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,EAAG;AACxB,MAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,YAAA,CAAa,2BAAA,EAA6B,SAAS,QAAQ,CAAA;AAClF,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,MAAA,CAAO,OAAO,CAAA;AAC9C,MAAA,MAAM,GAAA,GAAM,IAAA;AAAA,QACV,QAAA;AAAA,QACA,MAAM,QAAA,CACH,IAAA,CAAK,iBAAiB,EACtB,MAAA,CAAO;AAAA,UACN,MAAM,MAAA,CAAO,IAAA;AAAA,UACb,KAAA,EAAO,MAAA,CAAO,KAAA,EAAO,IAAA,EAAK,GAAI,MAAA,CAAO,KAAA,CAAM,IAAA,EAAK,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,GAAI,IAAA;AAAA,UAClE,SAAS,MAAA,CAAO,OAAA;AAAA,UAChB,KAAA,EAAO,QAAQ,KAAA,IAAS,IAAA;AAAA,UACxB,WAAA,EAAa,QAAQ,UAAA,IAAc,IAAA;AAAA,UACnC,QAAA,EAAU,QAAQ,QAAA,IAAY,IAAA;AAAA,UAC9B,MAAA,EAAQ,OAAA,CAAQ,MAAA,IAAU,EAAC;AAAA,UAC3B,OAAA,EAAS,OAAA,CAAQ,KAAA,IAAS;AAAC,SAC5B,CAAA,CACA,MAAA,CAAO,IAAI,EACX,MAAA;AAAO,OACZ;AACA,MAAA,MAAM,EAAA,GAAK,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA;AACxB,MAAA,IAAI,OAAA,GAAU,CAAA;AACd,MAAA,KAAA,MAAW,UAAA,IAAA,CAAe,OAAO,WAAA,IAAe,IAAI,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAG;AAC/D,QAAA,IAAI;AACF,UAAA,MAAM,KAAA,GAAQ,gBAAgB,UAAU,CAAA;AACxC,UAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,eAAe,CAAA;AAC3C,UAAA,MAAM,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,EAAE,IAAI,QAAA,EAAS,CAAE,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAC,CAAA,CAAA,EAAI,WAAW,UAAA,CAAW,WAAW,KAAK,KAAK,CAAA,CAAA;AACxH,UAAA,MAAM,SAAS,MAAM,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,CAAE,MAAA,CAAO,IAAA,EAAM,KAAA,EAAO,EAAE,WAAA,EAAa,UAAA,CAAW,WAAA,EAAa,MAAA,EAAQ,OAAO,CAAA;AAC7H,UAAA,IAAI,MAAA,CAAO,KAAA,EAAO,MAAM,MAAA,CAAO,KAAA;AAC/B,UAAA,IAAA;AAAA,YACE,QAAA;AAAA,YACA,MAAM,QAAA,CAAS,IAAA,CAAK,qBAAqB,EAAE,MAAA,CAAO;AAAA,cAChD,SAAA,EAAW,EAAA;AAAA,cACX,IAAA;AAAA,cACA,cAAc,UAAA,CAAW,WAAA;AAAA,cACzB,WAAW,KAAA,CAAM,UAAA;AAAA,cACjB,OAAO,UAAA,CAAW,KAAA,GAAQ,KAAK,KAAA,CAAM,UAAA,CAAW,KAAK,CAAA,GAAI,IAAA;AAAA,cACzD,QAAQ,UAAA,CAAW,MAAA,GAAS,KAAK,KAAA,CAAM,UAAA,CAAW,MAAM,CAAA,GAAI;AAAA,aAC7D;AAAA,WACH;AAAA,QACF,CAAA,CAAA,MAAQ;AACN,UAAA,OAAA,IAAW,CAAA;AAAA,QACb;AAAA,MACF;AACA,MAAA,OAAA,IAAW,KAAK,GAAA,CAAI,CAAA,EAAA,CAAI,OAAO,WAAA,EAAa,MAAA,IAAU,KAAK,CAAC,CAAA;AAC5D,MAAA,OAAO,EAAE,EAAA,EAAI,kBAAA,EAAoB,OAAA,EAAQ;AAAA,IAC3C,CAAA;AAAA,IAEA,MAAM,SAAA,CAAU,EAAE,QAAQ,EAAA,EAAG,GAAI,EAAC,EAAG;AACnC,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,EAAG;AACxB,MAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,EAAC;AACrB,MAAA,MAAM,IAAA,GAAO,IAAA;AAAA,QACX,WAAA;AAAA,QACA,MAAM,SAAS,IAAA,CAAK,iBAAiB,EAAE,MAAA,CAAO,cAAc,EAAE,EAAA,CAAG,SAAA,EAAW,MAAM,CAAA,CAAE,KAAA,CAAM,cAAc,EAAE,SAAA,EAAW,OAAO,CAAA,CAAE,MAAM,KAAK;AAAA,OAC3I;AACA,MAAA,OAAO,IAAA,CAAK,IAAI,QAAQ,CAAA;AAAA,IAC1B,CAAA;AAAA,IAEA,MAAM,OAAO,EAAA,EAAI;AACf,MAAA,MAAM,MAAM,IAAA,CAAiB,QAAA,EAAU,MAAM,QAAA,CAAS,KAAK,iBAAiB,CAAA,CAAE,MAAA,CAAO,cAAc,EAAE,EAAA,CAAG,IAAA,EAAM,EAAE,CAAA,CAAE,aAAa,CAAA;AAC/H,MAAA,OAAO,GAAA,GAAM,QAAA,CAAS,GAAG,CAAA,GAAI,IAAA;AAAA,IAC/B,CAAA;AAAA,IAEA,MAAM,QAAQ,QAAA,EAAU;AACtB,MAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAQ,CAAA;AACnC,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,IAAU,IAAA,EAAM;AACjC,QAAA,MAAM,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,OAAA,CAAQ,IAAI,CAAC,IAAA,CAAK,SAAS,KAAA,CAAM,cAAA,EAAgB,EAAE,KAAA,EAAO,GAAA,EAAK,CAAA,EAAG,EAAA,EAAI,CAAC,CAAA;AACpG,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,MAAA,IAAU,CAAC,CAAA,CAAE,SAAS,CAAA;AAC3E,QAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,SAAA,CAAU,QAAA,EAAU,CAAA,EAAG,MAAM,CAAC,CAAC,CAAA;AAAA,MACrE;AAEA,MAAA,MAAM,IAAA,GAAO,IAAA;AAAA,QACX,SAAA;AAAA,QACA,MAAM,QAAA,CAAS,IAAA,CAAK,iBAAiB,CAAA,CAAE,OAAO,6CAA6C,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA,CAAE,KAAA,CAAM,cAAc,EAAE,SAAA,EAAW,MAAM;AAAA,OAChK;AACA,MAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QACtB,EAAA,EAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AAAA,QACf,QAAA,EAAU,MAAA,CAAO,CAAA,CAAE,SAAS,CAAA;AAAA,QAC5B,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,EAAE,CAAA;AAAA,QACzB,SAAA,EAAW,EAAE,UAAA,KAAe,IAAA;AAAA,QAC5B,IAAA,EAAM,EAAE,UAAA,KAAe,IAAA;AAAA,QACvB,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,UAAU;AAAA,OAChC,CAAE,CAAA;AAAA,IACJ,CAAA;AAAA,IAEA,MAAM,MAAM,QAAA,EAAU,IAAA,EAAM,EAAE,QAAA,GAAW,KAAA,EAAM,GAAI,EAAC,EAAG;AACrD,MAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAQ,CAAA;AACnC,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,IAAU,IAAA,EAAM;AACjC,QAAA,MAAM,CAAC,IAAA,EAAM,MAAM,CAAA,GAAI,MAAM,QAAQ,GAAA,CAAI;AAAA,UACvC,KACG,IAAA,CAAK,EAAE,gBAAgB,KAAA,CAAM,cAAA,EAAgB,UAAU,UAAA,EAAW,EAAG,IAAA,EAAM,UAAA,EAAY,WAAW,WAAA,GAAc,UAAA,EAAY,CAAA,CAC5H,KAAA,CAAM,CAAC,KAAA,KAA+C;AACrD,YAAA,MAAM,IAAI,YAAA,CAAa,KAAA,EAAO,WAAW,UAAA,EAAY,KAAA,EAAO,MAAM,OAAO,CAAA;AAAA,UAC3E,CAAC,CAAA;AAAA,UACH,EAAA;AAAG,SACJ,CAAA;AACD,QAAA,OAAO,SAAA,CAAU,QAAA,EAAU,IAAA,EAAM,MAAM,CAAA;AAAA,MACzC;AACA,MAAA,IAAI,UAAU,MAAM,IAAI,YAAA,CAAa,6CAAA,EAA+C,SAAS,OAAO,CAAA;AACpG,MAAA,MAAM,GAAA,GAAM,IAAA;AAAA,QACV,OAAA;AAAA,QACA,MAAM,QAAA,CAAS,IAAA,CAAK,iBAAiB,EAAE,MAAA,CAAO,EAAE,SAAA,EAAW,QAAA,EAAU,MAAM,CAAA,CAAE,MAAA,CAAO,6CAA6C,EAAE,MAAA;AAAO,OAC5I;AACA,MAAA,OAAO,EAAE,EAAA,EAAI,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA,EAAG,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,SAAS,CAAA,EAAG,IAAA,EAAM,MAAA,CAAO,IAAI,IAAI,CAAA,EAAG,SAAA,EAAW,GAAA,CAAI,UAAA,KAAe,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,SAAA,EAAW,MAAA,CAAO,GAAA,CAAI,UAAU,CAAA,EAAE;AAAA,IAC1K,CAAA;AAAA,IAEA,MAAM,SAAS,QAAA,EAAU;AACvB,MAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAQ,CAAA;AACnC,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,IAAU,IAAA,EAAM;AAEjC,QAAA,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,cAAc,CAAA;AACxC,QAAA;AAAA,MACF;AACA,MAAA,IAAA,CAAK,UAAA,EAAY,MAAM,QAAA,CAAS,GAAA,CAAI,qBAAqB,EAAE,MAAA,EAAQ,QAAA,EAAU,CAAC,CAAA;AAAA,IAChF,CAAA;AAAA,IAEA,MAAA;AAAA,IAEA,MAAM,eAAe,QAAA,EAAU;AAC7B,MAAA,OAAA,CAAQ,MAAM,MAAA,CAAO,QAAQ,CAAA,EAAG,cAAA;AAAA,IAClC,CAAA;AAAA,IAEA,SAAA,CAAU,UAAU,QAAA,EAAU;AAC5B,MAAA,IAAI,OAAA,GAAU,KAAA;AACd,MAAA,IAAI,OAAO,MAAM;AAAA,MAAC,CAAA;AAClB,MAAA,KAAA,CAAM,YAAY;AAChB,QAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAQ,CAAA;AACnC,QAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,IAAU,CAAC,QAAQ,OAAA,EAAS;AAC/C,QAAA,MAAM,MAAM,IAAA,CAAK,qBAAA,CAAsB,MAAM,cAAA,EAAiB,MAAM,IAAG,IAAM,EAAA,EAAI,EAAE,QAAA,EAAU,MAAM,QAAA,EAAS,EAAG,QAAQ,MAAM,QAAA,IAAY,CAAA;AACzI,QAAA,IAAI,OAAA,MAAa,WAAA,EAAY;AAAA,aACxB,IAAA,GAAO,MAAM,GAAA,CAAI,WAAA,EAAY;AAAA,MACpC,CAAA,GAAG,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAC1B,MAAA,OAAO,MAAM;AACX,QAAA,OAAA,GAAU,IAAA;AACV,QAAA,IAAA,EAAK;AAAA,MACP,CAAA;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,OAAA,GAAU;AACd,MAAA,OAAO,KAAqB,SAAA,EAAW,MAAM,SAAS,GAAA,CAAI,kBAAkB,CAAC,CAAA,KAAM,IAAA;AAAA,IACrF,CAAA;AAAA,IAEA,MAAM,SAAA,CAAU,QAAA,EAAU,MAAA,EAAQ,YAAY,IAAA,EAAM;AAClD,MAAA,IAAA,CAAK,WAAA,EAAa,MAAM,QAAA,CAAS,GAAA,CAAI,2BAAA,EAA6B,EAAE,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAQ,OAAA,EAAS,SAAA,EAAW,CAAC,CAAA;AAAA,IACrH,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,QAAA,EAAU,MAAA,EAAQ;AAC7B,MAAA,IAAA,CAAK,QAAA,EAAU,MAAM,QAAA,CAAS,GAAA,CAAI,uBAAA,EAAyB,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAA,EAAU,MAAA,EAAQ,CAAC,CAAA;AAAA,IACpG,CAAA;AAAA,IAEA,MAAM,aAAa,QAAA,EAAU;AAC3B,MAAA,MAAM,OAAO,IAAA,CAAY,cAAA,EAAgB,MAAM,QAAA,CAAS,KAAK,iBAAiB,CAAA,CAAE,MAAA,EAAO,CAAE,GAAG,IAAA,EAAM,QAAQ,CAAA,CAAE,MAAA,CAAO,IAAI,CAAC,CAAA;AACxH,MAAA,IAAI,CAAC,KAAK,MAAA,EAAQ,MAAM,IAAI,YAAA,CAAa,6BAAA,EAA+B,SAAS,cAAc,CAAA;AAAA,IACjG,CAAA;AAAA,IAEA,MAAM,YAAY,QAAA,EAAU;AAC1B,MAAA,MAAM,IAAA,GAAO,IAAA;AAAA,QACX,aAAA;AAAA,QACA,MAAM,QAAA,CAAS,IAAA,CAAK,qBAAqB,CAAA,CAAE,MAAA,CAAO,uCAAuC,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA,CAAE,MAAM,YAAY;AAAA,OACzI;AACA,MAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,OAAO,EAAC;AAC1B,MAAA,MAAM,SAAS,MAAM,QAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,CAAE,gBAAA;AAAA,QACjD,KAAK,GAAA,CAAI,CAAC,MAAM,MAAA,CAAO,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,QAC9B;AAAA,OACF;AACA,MAAA,MAAM,OAAO,IAAI,GAAA,CAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,SAAS,CAAU,CAAC,CAAA;AACnF,MAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QACtB,EAAA,EAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AAAA,QACf,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAI,CAAA;AAAA,QACnB,WAAA,EAAa,MAAA,CAAO,CAAA,CAAE,YAAY,CAAA;AAAA,QAClC,KAAA,EAAQ,EAAE,KAAA,IAA2B,IAAA;AAAA,QACrC,MAAA,EAAS,EAAE,MAAA,IAA4B,IAAA;AAAA,QACvC,KAAK,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,CAAE,IAAI,CAAC,CAAA,IAAK;AAAA,OACnC,CAAE,CAAA;AAAA,IACJ,CAAA;AAAA,IAEA,MAAM,MAAM,EAAE,KAAA,GAAQ,KAAK,QAAA,EAAS,GAAI,EAAC,EAAG;AAC1C,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,EAAG;AACxB,MAAA,IAAI,KAAA,GAAQ,QAAA,CAAS,IAAA,CAAK,eAAe,CAAA,CAAE,OAAO,YAAY,CAAA,CAAE,GAAA,CAAI,QAAA,EAAU,QAAQ,CAAA;AACtF,MAAA,IAAI,UAAU,MAAA,EAAQ,KAAA,GAAQ,KAAA,CAAM,EAAA,CAAG,UAAU,QAAQ,CAAA;AACzD,MAAA,MAAM,IAAA,GAAO,KAAY,OAAA,EAAS,MAAM,MAAM,KAAA,CAAM,YAAA,EAAc,EAAE,SAAA,EAAW,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,cAAc,EAAE,SAAA,EAAW,OAAO,CAAA,CAAE,KAAA,CAAM,KAAK,CAAC,CAAA;AAC9I,MAAA,MAAM,QAAQ,MAAA,GAAS,IAAA,CAAY,OAAA,EAAS,MAAM,SAAS,IAAA,CAAK,oBAAoB,CAAA,CAAE,MAAA,CAAO,SAAS,CAAA,CAAE,EAAA,CAAG,WAAW,MAAM,CAAC,IAAI,EAAC;AAClI,MAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,MAAA,CAAO,CAAA,CAAE,OAAO,CAAC,CAAC,CAAA;AACzD,MAAA,MAAM,OAAA,GAAU,MAAA,GAAS,IAAA,CAAgB,OAAA,EAAS,MAAM,SAAS,GAAA,CAAI,qBAAqB,CAAC,CAAA,GAAI,EAAC;AAChG,MAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAA,CAAK,OAAA,IAAW,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,KAAM,MAAA,CAAO,OAAO,MAAM,QAAA,IAAY,CAAA,KAAM,IAAA,GAAO,MAAA,CAAO,MAAA,CAAO,CAAC,EAAE,CAAC,CAAA,GAAI,CAAC,CAAC,CAAC,CAAA;AACtH,MAAA,OAAO,IAAA,CAAK,IAAI,CAAC,CAAA,KAAM,OAAO,CAAA,EAAG,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,IAC/C,CAAA;AAAA,IAEA,MAAM,WAAA,CAAY,EAAE,KAAA,EAAO,MAAK,EAAG;AACjC,MAAA,MAAM,GAAA,GAAM,IAAA,CAAU,aAAA,EAAe,MAAM,QAAA,CAAS,KAAK,eAAe,CAAA,CAAE,MAAA,CAAO,EAAE,KAAA,EAAO,KAAA,CAAM,MAAK,EAAG,IAAA,EAAM,IAAA,EAAM,IAAA,EAAK,IAAK,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,YAAY,CAAA,CAAE,MAAA,EAAQ,CAAA;AACnK,MAAA,OAAO,MAAA,CAAO,GAAA,kBAAK,IAAI,GAAA,CAAI,CAAC,MAAA,CAAO,GAAA,CAAI,EAAE,CAAC,CAAC,CAAA,kBAAG,IAAI,KAAK,CAAA;AAAA,IACzD,CAAA;AAAA,IAEA,MAAM,KAAK,MAAA,EAAQ;AACjB,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,IAAA,CAAK,oBAAoB,EAAE,MAAA,CAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,CAAA;AAEnF,MAAA,IAAI,MAAA,CAAO,SAAS,MAAA,CAAO,KAAA,CAAM,SAAS,OAAA,EAAS,IAAA,CAAK,QAAQ,MAAM,CAAA;AAAA,IACxE,CAAA;AAAA,IAEA,MAAM,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,EAAG;AACxB,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,IAAA,CAAK,QAAA,EAAU,MAAM,QAAA,CAAS,IAAA,CAAK,oBAAoB,CAAA,CAAE,MAAA,EAAO,CAAE,EAAA,CAAG,WAAW,MAAM,CAAA,CAAE,EAAA,CAAG,SAAA,EAAW,MAAM,CAAC,CAAA;AAAA,IAC/G,CAAA;AAAA,IAEA,MAAM,YAAA,GAAe;AACnB,MAAA,MAAM,IAAA,GAAO,IAAA,CAAY,cAAA,EAAgB,MAAM,QAAA,CAAS,GAAA,CAAI,wBAAA,EAA0B,EAAE,QAAA,EAAU,EAAA,EAAI,CAAC,CAAA;AACvG,MAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAiB,IAAI,IAAI,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,KAAM,IAAI,EAAE,GAAA,CAAI,CAAC,MAAM,MAAA,CAAO,CAAA,CAAE,EAAE,CAAC,CAAC,CAAA;AACnG,MAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,KAAM,MAAA,CAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,GAAA,CAAI,OAAO,CAAC,CAAC,CAAA;AAAA,IAC7D;AAAA,GACF;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,kBAAA,CAAmB,QAAA,EAA2B,OAAA,GAAgC,EAAC,EAAiB;AAC9G,EAAA,MAAM,MAAA,GAAS,mBAAA,CAAoB,QAAA,EAAU,OAAO,CAAA;AACpD,EAAA,OAAO,EAAE,MAAA,EAAQ,CAAC,WAAW,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,EAAE;AACrD;AChfO,IAAM,gBAAA,GAAmB;AAczB,SAAS,YAAA,CAAa,WAAW,UAAA,EAA0B;AAChE,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,IAAK,UAAA;AAC5C,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,IAAA;AAAA,IACP,QAAA,EAAU,GAAG,IAAI,CAAA,IAAA,CAAA;AAAA,IACjB,MAAA,EAAQ,CAAC,EAAA,KAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,kBAAA,CAAmB,EAAE,CAAC,CAAA,CAAA;AAAA,IACjD,KAAA,EAAO,GAAG,IAAI,CAAA,MAAA,CAAA;AAAA,IACd,OAAA,EAAS,GAAG,IAAI,CAAA,QAAA;AAAA,GAClB;AACF;AAgBA,IAAM,OAAA,GAAU,gCAAA;AAGT,SAAS,oBAAA,CAAqB,OAAA,GAA+B,EAAC,EAA8B;AACjG,EAAA,MAAM,IAAA,GAAO,kBAAA,CAAmB,OAAA,CAAQ,IAAI,CAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,OAAA,CAAQ,QAAQ,CAAA;AAC3C,EAAA,MAAM,UAAU,OAAA,CAAQ,QAAA,KAAa,QAAQ,IAAA,GAAQ,OAAA,CAAQ,UAAU,OAAA,IAAW,SAAA;AAClF,EAAA,MAAM,WAA+B,OAAA,GACjC;AAAA,IACE,EAAE,EAAA,EAAI,uBAAA,EAAyB,OAAA,EAAS,OAAO,IAAA,CAAK,QAAA,CAAS,YAAA,EAAc,IAAA,EAAM,KAAA,CAAM,QAAA,EAAU,KAAA,EAAO,EAAA,EAAI,QAAQ,+BAAA,EAAgC;AAAA,IACpJ,EAAE,EAAA,EAAI,cAAA,EAAgB,OAAA,EAAS,KAAA,EAAO,KAAK,QAAA,CAAS,IAAA,EAAM,IAAA,EAAM,KAAA,CAAM,OAAO,KAAA,EAAO,EAAA,EAAI,QAAQ,sBAAA,EAAwB,WAAA,EAAa,QAAQ,eAAA;AAAgB,MAE/J,EAAC;AACL,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,EAAE,IAAA,EAAM,KAAA,CAAM,OAAO,WAAA,EAAa,wCAAA,EAA0C,UAAU,IAAA,EAAK;AAAA,IAC3F,EAAE,IAAA,EAAM,KAAA,CAAM,UAAU,WAAA,EAAa,kCAAA,EAAoC,UAAU,IAAA,EAAK;AAAA,IACxF,EAAE,MAAM,CAAA,EAAG,KAAA,CAAM,KAAK,CAAA,KAAA,CAAA,EAAS,WAAA,EAAa,2BAAA,EAA6B,QAAA,EAAU,IAAA,EAAK;AAAA,IACxF,GAAI,OAAA,CAAQ,KAAA,KAAU,KAAA,GAClB,EAAC,GACD;AAAA,MACE,EAAE,IAAA,EAAM,KAAA,CAAM,OAAO,WAAA,EAAa,4BAAA,EAA8B,UAAU,IAAA,EAAK;AAAA,MAC/E,EAAE,IAAA,EAAM,KAAA,CAAM,SAAS,WAAA,EAAa,yCAAA,EAA2C,UAAU,IAAA;AAAK;AAChG,GACN;AACA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA,EAAY,CAAC,EAAE,OAAA,EAAS,SAAS,IAAA,EAAM,qCAAA,EAAuC,IAAA,EAAM,gDAAA,EAAkD,CAAA;AAAA,IACtI,OAAA,EAAS;AAAA,MACP,QAAA,EAAU;AAAA,QACR,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QAClF,EAAE,IAAA,EAAM,kBAAA,EAAoB,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QAC3F,EAAE,IAAA,EAAM,oBAAA,EAAsB,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QAC7F,EAAE,IAAA,EAAM,kBAAA,EAAoB,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAAA,QAC3F,EAAE,IAAA,EAAM,uBAAA,EAAyB,QAAA,EAAU,CAAC,mBAAmB,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA;AAAM,OAClG;AAAA,MACA,KAAA,EAAO;AAAA,QACL,+HAAA;AAAA,QACA,oJAAA;AAAA,QACA;AAAA;AACF;AACF,GACF;AACF;AAGO,SAAS,kBAAA,CAAmB,OAAA,GAA+B,EAAC,EAAG;AACpE,EAAA,OAAO,WAAA,CAAY;AAAA,IACjB,EAAA,EAAI,gBAAA;AAAA,IACJ,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY,CAAC,SAAS,CAAA;AAAA,IACxC,WAAA,EAAa,qBAAqB,OAAO;AAAA,GAC1C,CAAA;AACH;AAOO,SAAS,sBAAA,CAAuB,OAAA,GAA4C,EAAC,EAAG;AACrF,EAAA,OAAO,WAAA,CAAY;AAAA,IACjB,EAAA,EAAI,qBAAA;AAAA,IACJ,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY,CAAC,kBAAkB,MAAM,CAAA;AAAA,IACvD,WAAA,EAAa;AAAA,MACX,UAAA,EAAY;AAAA,QACV,EAAE,OAAA,EAAS,+BAAA,EAAiC,IAAA,EAAM,0CAAA,EAA4C,MAAM,qDAAA;AAAsD;AAC5J;AACF,GACD,CAAA;AACH","file":"chunk-QNUAKW7K.js","sourcesContent":["/**\n * Support threads on genesis-chat (the `support-chat` block, migration\n * `0001_genesis_support_chat.sql` in this package): what the support client\n * needs from a genesis-chat client, as a structural type, so genesis-support\n * never imports genesis-chat. Pass `createChatClient(supabase)` from\n * `@marktiderman/genesis-chat` as the support client's `chat` option.\n */\n\n/** The block's FeatureId. */\nexport const SUPPORT_CHAT_BLOCK_ID = \"support-chat\" as const;\n\n/** The genesis_chat kind a report's conversation is, and its subject type. */\nexport const SUPPORT_CHAT_KIND = \"support\";\nexport const SUPPORT_CHAT_SUBJECT = \"support_report\";\n\n/** A file a chat message carries (a subset of genesis-chat's ChatAttachment). */\nexport interface SupportChatAttachment {\n id: string;\n kind: string;\n path: string | null;\n url: string | null;\n mime: string | null;\n name: string | null;\n width: number | null;\n height: number | null;\n durationMs: number | null;\n}\n\n/** A chat message (a subset of genesis-chat's ChatMessage). */\nexport interface SupportChatMessage {\n id: string;\n seq: number;\n senderId: string | null;\n senderRole: string;\n kind: string;\n body: string;\n visibility: \"everyone\" | \"observers\";\n createdAt: string;\n deletedAt: string | null;\n attachments: SupportChatAttachment[];\n}\n\n/** The part of genesis-chat's client (`createChatClient`) a support thread uses. */\nexport interface SupportChatClient {\n messages(conversationId: string, opts?: { limit?: number }): Promise<{ messages: SupportChatMessage[]; hasEarlier: boolean }>;\n send(input: { conversationId: string; clientId: string; body?: string; visibility?: \"everyone\" | \"observers\" }): Promise<SupportChatMessage>;\n markRead(conversationId: string, seq?: number): Promise<unknown>;\n fileUrls(paths: string[]): Promise<Record<string, string>>;\n subscribeConversation(\n conversationId: string,\n me: string,\n handlers: { onSignal: (signal: unknown) => void; onJoin?: () => void },\n ): { unsubscribe(): void };\n}\n\n/** Where a report's thread lives: a genesis-chat conversation, or support_replies (no support-chat block). */\nexport type SupportThreadInfo = { mode: \"chat\"; conversationId: string } | { mode: \"replies\"; conversationId: null };\n\n/** A support report's status, as genesis-chat's conversation status (the migration's mapping). */\nexport const SUPPORT_TO_CHAT_STATUS = {\n open: \"open\",\n submitted: \"open\",\n in_progress: \"pending\",\n addressed: \"solved\",\n wont_fix: \"closed\",\n} as const;\n\n/** PostgREST's \"no such function\" (PGRST202) or Postgres's (42883): the block's migration isn't applied. */\nexport function isMissingFunction(error: { code?: string; message?: string } | null | undefined): boolean {\n if (!error) return false;\n return error.code === \"PGRST202\" || error.code === \"42883\";\n}\n","/**\n * The support client: the app's own Supabase (migration 0006 from\n * `@marktiderman/genesis-supabase`) under row level security. Everything a\n * person does (send feedback, read their reports and the replies, reply, vote\n * on the idea board) and the Supabase sink for the feedback record.\n *\n * With a genesis-chat client (`chat` option) and the support-chat block's\n * migration applied, a report's thread runs on genesis-chat: replies, reply,\n * markRead and subscribe go through the report's conversation (live updates,\n * files, internal notes for staff). Otherwise they use support_replies.\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { FeedbackStatus } from \"./contract\";\nimport { attachmentBytes, sanitizeContext, type FeedbackDelivery, type FeedbackRecord, type FeedbackSink, type FeedbackType } from \"./record\";\nimport { isMissingFunction, type SupportChatAttachment, type SupportChatClient, type SupportChatMessage, type SupportThreadInfo } from \"./chat\";\n\n/** Any supabase-js client; the app's generated Database types don't need to list the support tables. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type SupportSupabase = SupabaseClient<any, any, any>;\n\n/** The storage bucket migration 0006 creates. */\nexport const SUPPORT_BUCKET = \"genesis-support\";\n\n/** A Supabase refusal, with its code (`GS429` rate limited, `42501` not allowed, …). */\nexport class SupportError extends Error {\n constructor(\n message: string,\n readonly code: string | undefined,\n readonly operation: string,\n ) {\n super(message);\n this.name = \"SupportError\";\n }\n /** The database's rate limit (SQLSTATE GS429). */\n get rateLimited(): boolean {\n return this.code === \"GS429\";\n }\n}\n\nfunction must<T>(operation: string, result: { data: T | null; error: { message: string; code?: string } | null }): T {\n if (result.error) throw new SupportError(result.error.message, result.error.code, operation);\n return result.data as T;\n}\n\nexport type IdeaStatus = \"open\" | \"planned\" | \"in_progress\" | \"shipped\" | \"declined\" | \"hidden\";\n\nexport interface SupportReport {\n id: string;\n type: FeedbackType;\n title: string | null;\n message: string;\n status: FeedbackStatus;\n /** The FeatureId staff linked it to. */\n featureId: string | null;\n route: string | null;\n appVersion: string | null;\n createdAt: string;\n updatedAt: string;\n lastStaffReplyAt: string | null;\n lastUserReplyAt: string | null;\n userReadAt: string | null;\n staffReadAt: string | null;\n /** A staff reply the person hasn't read. */\n unread: boolean;\n /** Who sent it (the person's own id, or for staff the reporter's). */\n userId?: string | null;\n}\n\nexport interface SupportReply {\n id: string;\n reportId: string;\n body: string;\n fromStaff: boolean;\n /** Written by the signed-in person. */\n mine: boolean;\n createdAt: string;\n /** A staff-only internal note (support-chat block; staff only ever read them). Always false on support_replies. */\n internal?: boolean;\n /** Photos, files and voice notes (support-chat block). Empty on support_replies. */\n attachments?: SupportReplyAttachment[];\n /** Its number in the conversation (support-chat block), else null. */\n seq?: number | null;\n}\n\n/** A file on a reply, with a signed URL when it is in storage. */\nexport interface SupportReplyAttachment extends Omit<SupportChatAttachment, \"url\"> {\n /** A signed URL (or the attachment's own https URL); null when it couldn't be signed. */\n url: string | null;\n}\n\nexport interface SupportAttachmentView {\n id: string;\n path: string;\n contentType: string;\n width: number | null;\n height: number | null;\n /** A signed URL, valid for `signedUrlSeconds` (default an hour). Null when signing failed. */\n url: string | null;\n}\n\nexport interface SupportIdea {\n id: string;\n title: string;\n body: string | null;\n status: IdeaStatus;\n /** The FeatureId that delivers it (set when it ships). */\n featureId: string | null;\n shippedAt: string | null;\n voteCount: number;\n /** The signed-in person voted for it. */\n voted: boolean;\n /** The signed-in person suggested it. */\n mine: boolean;\n createdAt: string;\n}\n\nexport interface SupportClient extends FeedbackSink {\n /** The signed-in person's reports, newest first. */\n myReports(options?: { limit?: number }): Promise<SupportReport[]>;\n report(id: string): Promise<SupportReport | null>;\n replies(reportId: string): Promise<SupportReply[]>;\n /** `internal`: a staff-only note (support-chat block, staff only). */\n reply(reportId: string, body: string, options?: { internal?: boolean }): Promise<SupportReply>;\n markRead(reportId: string): Promise<void>;\n /**\n * Where the report's thread lives: its genesis-chat conversation when a `chat` client was given\n * and the support-chat block is installed, else support_replies.\n */\n thread(reportId: string): Promise<SupportThreadInfo>;\n /** The report's genesis-chat conversation id, or null (no chat client, no block, or not readable). */\n conversationId(reportId: string): Promise<string | null>;\n /** Live updates for the thread (chat only): `onChange` runs on each hint and each (re)join. Returns the stop. */\n subscribe(reportId: string, onChange: () => void): () => void;\n /** Whether the signed-in person is support staff. */\n isStaff(): Promise<boolean>;\n /** Staff: the report's status, and the FeatureId it is about (null keeps it, \"\" clears it). */\n setStatus(reportId: string, status: FeedbackStatus, featureId?: string | null): Promise<void>;\n /** Staff (support-chat block): who has the report, a staff member or null. */\n assign(reportId: string, userId: string | null): Promise<void>;\n /** Deletes the person's own report; its screenshots are queued for removal. */\n deleteReport(reportId: string): Promise<void>;\n attachments(reportId: string): Promise<SupportAttachmentView[]>;\n /** The board, most votes first. Hidden ideas are left out, the person's own included. */\n ideas(options?: { limit?: number; statuses?: IdeaStatus[] }): Promise<SupportIdea[]>;\n suggestIdea(input: { title: string; body?: string }): Promise<SupportIdea>;\n vote(ideaId: string): Promise<void>;\n unvote(ideaId: string): Promise<void>;\n /** \"You asked, we shipped\": shipped ideas the person suggested or voted for. */\n shippedForMe(): Promise<SupportIdea[]>;\n}\n\nexport interface SupportClientOptions {\n /**\n * A genesis-chat client (`createChatClient(supabase)`): with it, and the support-chat block's\n * migration applied, a report's thread runs on genesis-chat. Without either, support_replies.\n */\n chat?: SupportChatClient | false;\n /** Told once when a chat client was given but the block's migration isn't applied. Default `console.warn`. */\n onChatUnavailable?: (message: string) => void;\n bucket?: string;\n signedUrlSeconds?: number;\n /** For the attachment file names. Default `crypto.randomUUID`. */\n randomId?: () => string;\n}\n\nconst REPORT_COLUMNS =\n \"id, user_id, type, title, message, status, feature_id, route, app_version, created_at, updated_at, last_staff_reply_at, last_user_reply_at, user_read_at, staff_read_at\";\nconst IDEA_COLUMNS = \"id, title, body, status, feature_id, shipped_at, vote_count, created_at\";\n\ntype Row = Record<string, unknown>;\n\n/** True when there is a staff reply newer than the person's last read. */\nexport function isUnread(row: { lastStaffReplyAt: string | null; userReadAt: string | null }): boolean {\n if (!row.lastStaffReplyAt) return false;\n return !row.userReadAt || new Date(row.lastStaffReplyAt).getTime() > new Date(row.userReadAt).getTime();\n}\n\nfunction toReport(r: Row): SupportReport {\n const report: Omit<SupportReport, \"unread\"> = {\n id: String(r.id),\n type: r.type as FeedbackType,\n title: (r.title as string | null) ?? null,\n message: String(r.message ?? \"\"),\n status: r.status as FeedbackStatus,\n featureId: (r.feature_id as string | null) ?? null,\n route: (r.route as string | null) ?? null,\n appVersion: (r.app_version as string | null) ?? null,\n createdAt: String(r.created_at),\n updatedAt: String(r.updated_at),\n lastStaffReplyAt: (r.last_staff_reply_at as string | null) ?? null,\n lastUserReplyAt: (r.last_user_reply_at as string | null) ?? null,\n userReadAt: (r.user_read_at as string | null) ?? null,\n staffReadAt: (r.staff_read_at as string | null) ?? null,\n userId: (r.user_id as string | null) ?? null,\n };\n return { ...report, unread: isUnread(report) };\n}\n\nfunction toIdea(r: Row, mine: ReadonlySet<string>, voted: ReadonlySet<string>): SupportIdea {\n return {\n id: String(r.id),\n title: String(r.title ?? \"\"),\n body: (r.body as string | null) ?? null,\n status: r.status as IdeaStatus,\n featureId: (r.feature_id as string | null) ?? null,\n shippedAt: (r.shipped_at as string | null) ?? null,\n voteCount: Number(r.vote_count ?? 0),\n voted: voted.has(String(r.id)),\n mine: mine.has(String(r.id)),\n createdAt: String(r.created_at),\n };\n}\n\nconst EXTENSIONS: Record<string, string> = { \"image/png\": \"png\", \"image/jpeg\": \"jpg\", \"image/webp\": \"webp\" };\n\nfunction randomUuid(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n const hex = Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16));\n hex[12] = \"4\";\n hex[16] = ((parseInt(hex[16]!, 16) & 3) | 8).toString(16);\n const h = hex.join(\"\");\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n}\n\nfunction defaultRandomId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** The support client over the app's Supabase client. */\nexport function createSupportClient(supabase: SupportSupabase, options: SupportClientOptions = {}): SupportClient {\n const bucket = options.bucket ?? SUPPORT_BUCKET;\n const signedUrlSeconds = options.signedUrlSeconds ?? 3600;\n const randomId = options.randomId ?? defaultRandomId;\n\n const me = async (): Promise<string | null> => {\n const { data } = await supabase.auth.getSession();\n return data.session?.user?.id ?? null;\n };\n\n // The support-chat block: learned on first use (the database function is there or not).\n const chat = options.chat || null;\n let chatInstalled: boolean | null = chat ? null : false;\n const conversations = new Map<string, string>();\n const warn = options.onChatUnavailable ?? ((message: string) => console.warn(message));\n\n async function thread(reportId: string): Promise<SupportThreadInfo> {\n if (!chat || chatInstalled === false) return { mode: \"replies\", conversationId: null };\n const known = conversations.get(reportId);\n if (known) return { mode: \"chat\", conversationId: known };\n const result = await supabase.rpc(\"support_chat_conversation\", { report: reportId });\n if (result.error) {\n if (isMissingFunction(result.error)) {\n chatInstalled = false;\n warn(\n \"genesis-support: a chat client was given, but the support-chat block's migration (0001_genesis_support_chat.sql) isn't applied. Report threads use support_replies.\",\n );\n return { mode: \"replies\", conversationId: null };\n }\n throw new SupportError(result.error.message, result.error.code, \"thread\");\n }\n chatInstalled = true;\n const id = typeof result.data === \"string\" && result.data ? result.data : null;\n // No conversation (not readable, or a report the block hasn't reached): support_replies still mirrors it.\n if (!id) return { mode: \"replies\", conversationId: null };\n conversations.set(reportId, id);\n return { mode: \"chat\", conversationId: id };\n }\n\n async function chatReply(reportId: string, m: SupportChatMessage, userId: string | null): Promise<SupportReply> {\n const paths = m.attachments.map((a) => a.path).filter((p): p is string => Boolean(p));\n const signed = paths.length && chat ? await chat.fileUrls(paths).catch(() => ({}) as Record<string, string>) : {};\n return {\n id: m.id,\n reportId,\n body: m.body,\n fromStaff: m.senderRole === \"observer\",\n mine: Boolean(userId) && m.senderId === userId,\n createdAt: m.createdAt,\n internal: m.visibility === \"observers\",\n seq: m.seq,\n attachments: m.attachments.map((a) => ({ ...a, url: (a.path ? signed[a.path] : null) ?? a.url ?? null })),\n };\n }\n\n const client: SupportClient = {\n async submit(record: FeedbackRecord): Promise<FeedbackDelivery> {\n const userId = await me();\n if (!userId) throw new SupportError(\"Sign in to send feedback.\", \"42501\", \"submit\");\n const context = sanitizeContext(record.context);\n const row = must<Row>(\n \"submit\",\n await supabase\n .from(\"support_reports\")\n .insert({\n type: record.type,\n title: record.title?.trim() ? record.title.trim().slice(0, 300) : null,\n message: record.message,\n route: context.route ?? null,\n app_version: context.appVersion ?? null,\n platform: context.platform ?? null,\n device: context.device ?? {},\n context: context.extra ?? {},\n })\n .select(\"id\")\n .single(),\n );\n const id = String(row.id);\n let dropped = 0;\n for (const attachment of (record.attachments ?? []).slice(0, 5)) {\n try {\n const bytes = attachmentBytes(attachment);\n if (!bytes) throw new Error(\"no image data\");\n const path = `${userId}/${id}/${randomId().replace(/[^A-Za-z0-9_-]/g, \"\")}.${EXTENSIONS[attachment.contentType] ?? \"png\"}`;\n const upload = await supabase.storage.from(bucket).upload(path, bytes, { contentType: attachment.contentType, upsert: false });\n if (upload.error) throw upload.error;\n must(\n \"attach\",\n await supabase.from(\"support_attachments\").insert({\n report_id: id,\n path,\n content_type: attachment.contentType,\n byte_size: bytes.byteLength,\n width: attachment.width ? Math.round(attachment.width) : null,\n height: attachment.height ? Math.round(attachment.height) : null,\n }),\n );\n } catch {\n dropped += 1;\n }\n }\n dropped += Math.max(0, (record.attachments?.length ?? 0) - 5);\n return { id, attachmentsDropped: dropped };\n },\n\n async myReports({ limit = 50 } = {}) {\n const userId = await me();\n if (!userId) return [];\n const rows = must<Row[]>(\n \"myReports\",\n await supabase.from(\"support_reports\").select(REPORT_COLUMNS).eq(\"user_id\", userId).order(\"created_at\", { ascending: false }).limit(limit),\n );\n return rows.map(toReport);\n },\n\n async report(id) {\n const row = must<Row | null>(\"report\", await supabase.from(\"support_reports\").select(REPORT_COLUMNS).eq(\"id\", id).maybeSingle());\n return row ? toReport(row) : null;\n },\n\n async replies(reportId) {\n const where = await thread(reportId);\n if (where.mode === \"chat\" && chat) {\n const [page, userId] = await Promise.all([chat.messages(where.conversationId, { limit: 200 }), me()]);\n const shown = page.messages.filter((m) => m.kind === \"text\" && !m.deletedAt);\n return Promise.all(shown.map((m) => chatReply(reportId, m, userId)));\n }\n // Who wrote a reply isn't readable (staff stay anonymous): on the person's own report, theirs are the non-staff ones.\n const rows = must<Row[]>(\n \"replies\",\n await supabase.from(\"support_replies\").select(\"id, report_id, body, from_staff, created_at\").eq(\"report_id\", reportId).order(\"created_at\", { ascending: true }),\n );\n return rows.map((r) => ({\n id: String(r.id),\n reportId: String(r.report_id),\n body: String(r.body ?? \"\"),\n fromStaff: r.from_staff === true,\n mine: r.from_staff !== true,\n createdAt: String(r.created_at),\n }));\n },\n\n async reply(reportId, body, { internal = false } = {}) {\n const where = await thread(reportId);\n if (where.mode === \"chat\" && chat) {\n const [sent, userId] = await Promise.all([\n chat\n .send({ conversationId: where.conversationId, clientId: randomUuid(), body, visibility: internal ? \"observers\" : \"everyone\" })\n .catch((error: { message?: string; code?: string }) => {\n throw new SupportError(error?.message ?? \"Not sent\", error?.code, \"reply\");\n }),\n me(),\n ]);\n return chatReply(reportId, sent, userId);\n }\n if (internal) throw new SupportError(\"Internal notes need the support-chat block.\", \"0A000\", \"reply\");\n const row = must<Row>(\n \"reply\",\n await supabase.from(\"support_replies\").insert({ report_id: reportId, body }).select(\"id, report_id, body, from_staff, created_at\").single(),\n );\n return { id: String(row.id), reportId: String(row.report_id), body: String(row.body), fromStaff: row.from_staff === true, mine: true, createdAt: String(row.created_at) };\n },\n\n async markRead(reportId) {\n const where = await thread(reportId);\n if (where.mode === \"chat\" && chat) {\n // The migration carries the conversation's read place to the report.\n await chat.markRead(where.conversationId);\n return;\n }\n must(\"markRead\", await supabase.rpc(\"support_mark_read\", { report: reportId }));\n },\n\n thread,\n\n async conversationId(reportId) {\n return (await thread(reportId)).conversationId;\n },\n\n subscribe(reportId, onChange) {\n let stopped = false;\n let stop = () => {};\n void (async () => {\n const where = await thread(reportId);\n if (where.mode !== \"chat\" || !chat || stopped) return;\n const sub = chat.subscribeConversation(where.conversationId, (await me()) ?? \"\", { onSignal: () => onChange(), onJoin: () => onChange() });\n if (stopped) sub.unsubscribe();\n else stop = () => sub.unsubscribe();\n })().catch(() => undefined);\n return () => {\n stopped = true;\n stop();\n };\n },\n\n async isStaff() {\n return must<boolean | null>(\"isStaff\", await supabase.rpc(\"support_is_staff\")) === true;\n },\n\n async setStatus(reportId, status, featureId = null) {\n must(\"setStatus\", await supabase.rpc(\"support_set_report_status\", { report: reportId, status, feature: featureId }));\n },\n\n async assign(reportId, userId) {\n must(\"assign\", await supabase.rpc(\"support_assign_report\", { report: reportId, assignee: userId }));\n },\n\n async deleteReport(reportId) {\n const rows = must<Row[]>(\"deleteReport\", await supabase.from(\"support_reports\").delete().eq(\"id\", reportId).select(\"id\"));\n if (!rows.length) throw new SupportError(\"That report wasn't deleted.\", \"42501\", \"deleteReport\");\n },\n\n async attachments(reportId) {\n const rows = must<Row[]>(\n \"attachments\",\n await supabase.from(\"support_attachments\").select(\"id, path, content_type, width, height\").eq(\"report_id\", reportId).order(\"created_at\"),\n );\n if (!rows.length) return [];\n const signed = await supabase.storage.from(bucket).createSignedUrls(\n rows.map((r) => String(r.path)),\n signedUrlSeconds,\n );\n const urls = new Map((signed.data ?? []).map((s) => [s.path, s.signedUrl] as const));\n return rows.map((r) => ({\n id: String(r.id),\n path: String(r.path),\n contentType: String(r.content_type),\n width: (r.width as number | null) ?? null,\n height: (r.height as number | null) ?? null,\n url: urls.get(String(r.path)) ?? null,\n }));\n },\n\n async ideas({ limit = 100, statuses } = {}) {\n const userId = await me();\n let query = supabase.from(\"support_ideas\").select(IDEA_COLUMNS).neq(\"status\", \"hidden\");\n if (statuses?.length) query = query.in(\"status\", statuses);\n const rows = must<Row[]>(\"ideas\", await query.order(\"vote_count\", { ascending: false }).order(\"created_at\", { ascending: false }).limit(limit));\n const votes = userId ? must<Row[]>(\"ideas\", await supabase.from(\"support_idea_votes\").select(\"idea_id\").eq(\"user_id\", userId)) : [];\n const voted = new Set(votes.map((v) => String(v.idea_id)));\n const mineIds = userId ? must<unknown[]>(\"ideas\", await supabase.rpc(\"support_my_idea_ids\")) : [];\n const mine = new Set((mineIds ?? []).map((v) => String(typeof v === \"object\" && v !== null ? Object.values(v)[0] : v)));\n return rows.map((r) => toIdea(r, mine, voted));\n },\n\n async suggestIdea({ title, body }) {\n const row = must<Row>(\"suggestIdea\", await supabase.from(\"support_ideas\").insert({ title: title.trim(), body: body?.trim() || null }).select(IDEA_COLUMNS).single());\n return toIdea(row, new Set([String(row.id)]), new Set());\n },\n\n async vote(ideaId) {\n const result = await supabase.from(\"support_idea_votes\").insert({ idea_id: ideaId });\n // Voting twice is already voting.\n if (result.error && result.error.code !== \"23505\") must(\"vote\", result);\n },\n\n async unvote(ideaId) {\n const userId = await me();\n if (!userId) return;\n must(\"unvote\", await supabase.from(\"support_idea_votes\").delete().eq(\"idea_id\", ideaId).eq(\"user_id\", userId));\n },\n\n async shippedForMe() {\n const rows = must<Row[]>(\"shippedForMe\", await supabase.rpc(\"support_shipped_for_me\", { max_rows: 50 }));\n const ids = (pick: string) => new Set(rows.filter((r) => r[pick] === true).map((r) => String(r.id)));\n return rows.map((r) => toIdea(r, ids(\"mine\"), ids(\"voted\")));\n },\n };\n return client;\n}\n\n/** The Supabase sink on its own (the support client is one). */\nexport function createSupabaseSink(supabase: SupportSupabase, options: SupportClientOptions = {}): FeedbackSink {\n const client = createSupportClient(supabase, options);\n return { submit: (record) => client.submit(record) };\n}\n","/**\n * The support block as a `defineBlock` declaration: what it adds to an app\n * (two Settings rows, its routes, migration 0006) and what it means for the\n * stores' privacy forms. Platform-neutral; `createSupport` (native) builds\n * on it, and a web app can resolve it with `resolveBlocks` too.\n */\nimport { defineBlock, type BlockContributions, type BlockSettingsRow } from \"@marktiderman/genesis-features/blocks\";\nimport { resolveSupportCopy, type SupportCopyOverrides } from \"./copy\";\nimport { SUPPORT_CHAT_BLOCK_ID } from \"./chat\";\n\n/** The FeatureId of the support block (features.ts). */\nexport const SUPPORT_BLOCK_ID = \"support-feedback\";\n\n/** Where the block's screens live, under `basePath`. */\nexport interface SupportPaths {\n /** Help: the person's reports and links to the idea board. */\n inbox: string;\n /** The feedback form. */\n feedback: string;\n /** One report's thread: `thread(id)`. */\n thread: (id: string) => string;\n ideas: string;\n shipped: string;\n}\n\nexport function supportPaths(basePath = \"/support\"): SupportPaths {\n const base = basePath.replace(/\\/$/, \"\") || \"/support\";\n return {\n inbox: base,\n feedback: `${base}/new`,\n thread: (id) => `${base}/${encodeURIComponent(id)}`,\n ideas: `${base}/ideas`,\n shipped: `${base}/shipped`,\n };\n}\n\nexport interface SupportBlockOptions {\n /** Default `/support`. The app owns the route files under it. */\n basePath?: string;\n copy?: SupportCopyOverrides;\n /** The Settings rows' section (default `support`), or `false` for none. */\n settings?: false | { section?: string };\n /** A subtitle hook for the Help row (an unread count). Called on every Settings render. */\n useHelpSubtitle?: () => string | null | undefined;\n /** `false` leaves the idea board out. Default on. */\n ideas?: boolean;\n /** The block's other FeatureIds it needs (default `[\"account\"]`: a signed-in person). */\n requires?: readonly string[];\n}\n\nconst PACKAGE = \"@marktiderman/genesis-supabase\";\n\n/** The block's contributions, for any platform. */\nexport function supportContributions(options: SupportBlockOptions = {}): BlockContributions<never> {\n const copy = resolveSupportCopy(options.copy);\n const paths = supportPaths(options.basePath);\n const section = options.settings === false ? null : (options.settings?.section ?? \"support\");\n const settings: BlockSettingsRow[] = section\n ? [\n { id: \"support-send-feedback\", section, title: copy.settings.sendFeedback, href: paths.feedback, order: 10, testID: \"genesis-support-send-feedback\" },\n { id: \"support-help\", section, title: copy.settings.help, href: paths.inbox, order: 20, testID: \"genesis-support-help\", useSubtitle: options.useHelpSubtitle },\n ]\n : [];\n const routes = [\n { path: paths.inbox, description: \"the person's reports and staff replies\", signedIn: true },\n { path: paths.feedback, description: \"send feedback, with a screenshot\", signedIn: true },\n { path: `${paths.inbox}/[id]`, description: \"one report's conversation\", signedIn: true },\n ...(options.ideas === false\n ? []\n : [\n { path: paths.ideas, description: \"the idea board, with votes\", signedIn: true },\n { path: paths.shipped, description: \"ideas the person asked for that shipped\", signedIn: true },\n ]),\n ];\n return {\n settings,\n routes,\n migrations: [{ package: PACKAGE, file: \"migrations/0006_genesis_support.sql\", test: \"migrations/tests/0006_genesis_support.test.sql\" }],\n privacy: {\n collects: [\n { type: \"user ID\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"customer support\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"other user content\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"photos or videos\", purposes: [\"app functionality\"], linked: true, tracking: false },\n { type: \"other diagnostic data\", purposes: [\"app functionality\"], linked: true, tracking: false },\n ],\n notes: [\n \"Feedback is sent only when the person taps Send, and a screenshot only when they include it, after they can hide parts of it.\",\n \"Reports, screenshots and replies are visible to the person and the app's support staff only, and go when the person deletes them or their account.\",\n \"Feedback includes the app version, the screen's path (never its query string; private-looking segments are replaced), the platform and basic device facts.\",\n ],\n },\n };\n}\n\n/** The platform-neutral declaration. */\nexport function defineSupportBlock(options: SupportBlockOptions = {}) {\n return defineBlock({\n id: SUPPORT_BLOCK_ID,\n requires: options.requires ?? [\"account\"],\n contributes: supportContributions(options),\n });\n}\n\n/**\n * The support-chat block: a report's conversation runs on genesis-chat. It adds no screens of\n * its own (the support block's thread route renders the conversation); it brings migration\n * `0001_genesis_support_chat.sql` from this package, after 0006 and genesis-chat's migrations.\n */\nexport function defineSupportChatBlock(options: { requires?: readonly string[] } = {}) {\n return defineBlock({\n id: SUPPORT_CHAT_BLOCK_ID,\n requires: options.requires ?? [SUPPORT_BLOCK_ID, \"chat\"],\n contributes: {\n migrations: [\n { package: \"@marktiderman/genesis-support\", file: \"migrations/0001_genesis_support_chat.sql\", test: \"migrations/tests/0001_genesis_support_chat.test.sql\" },\n ],\n },\n });\n}\n"]}
@@ -224,11 +224,98 @@ declare function base64ToBytes(input: string): Uint8Array;
224
224
  /** The bytes of an attachment, from `bytes` or `base64`. */
225
225
  declare function attachmentBytes(attachment: FeedbackAttachment): Uint8Array | null;
226
226
 
227
+ /**
228
+ * Support threads on genesis-chat (the `support-chat` block, migration
229
+ * `0001_genesis_support_chat.sql` in this package): what the support client
230
+ * needs from a genesis-chat client, as a structural type, so genesis-support
231
+ * never imports genesis-chat. Pass `createChatClient(supabase)` from
232
+ * `@marktiderman/genesis-chat` as the support client's `chat` option.
233
+ */
234
+ /** The block's FeatureId. */
235
+ declare const SUPPORT_CHAT_BLOCK_ID: "support-chat";
236
+ /** The genesis_chat kind a report's conversation is, and its subject type. */
237
+ declare const SUPPORT_CHAT_KIND = "support";
238
+ declare const SUPPORT_CHAT_SUBJECT = "support_report";
239
+ /** A file a chat message carries (a subset of genesis-chat's ChatAttachment). */
240
+ interface SupportChatAttachment {
241
+ id: string;
242
+ kind: string;
243
+ path: string | null;
244
+ url: string | null;
245
+ mime: string | null;
246
+ name: string | null;
247
+ width: number | null;
248
+ height: number | null;
249
+ durationMs: number | null;
250
+ }
251
+ /** A chat message (a subset of genesis-chat's ChatMessage). */
252
+ interface SupportChatMessage {
253
+ id: string;
254
+ seq: number;
255
+ senderId: string | null;
256
+ senderRole: string;
257
+ kind: string;
258
+ body: string;
259
+ visibility: "everyone" | "observers";
260
+ createdAt: string;
261
+ deletedAt: string | null;
262
+ attachments: SupportChatAttachment[];
263
+ }
264
+ /** The part of genesis-chat's client (`createChatClient`) a support thread uses. */
265
+ interface SupportChatClient {
266
+ messages(conversationId: string, opts?: {
267
+ limit?: number;
268
+ }): Promise<{
269
+ messages: SupportChatMessage[];
270
+ hasEarlier: boolean;
271
+ }>;
272
+ send(input: {
273
+ conversationId: string;
274
+ clientId: string;
275
+ body?: string;
276
+ visibility?: "everyone" | "observers";
277
+ }): Promise<SupportChatMessage>;
278
+ markRead(conversationId: string, seq?: number): Promise<unknown>;
279
+ fileUrls(paths: string[]): Promise<Record<string, string>>;
280
+ subscribeConversation(conversationId: string, me: string, handlers: {
281
+ onSignal: (signal: unknown) => void;
282
+ onJoin?: () => void;
283
+ }): {
284
+ unsubscribe(): void;
285
+ };
286
+ }
287
+ /** Where a report's thread lives: a genesis-chat conversation, or support_replies (no support-chat block). */
288
+ type SupportThreadInfo = {
289
+ mode: "chat";
290
+ conversationId: string;
291
+ } | {
292
+ mode: "replies";
293
+ conversationId: null;
294
+ };
295
+ /** A support report's status, as genesis-chat's conversation status (the migration's mapping). */
296
+ declare const SUPPORT_TO_CHAT_STATUS: {
297
+ readonly open: "open";
298
+ readonly submitted: "open";
299
+ readonly in_progress: "pending";
300
+ readonly addressed: "solved";
301
+ readonly wont_fix: "closed";
302
+ };
303
+ /** PostgREST's "no such function" (PGRST202) or Postgres's (42883): the block's migration isn't applied. */
304
+ declare function isMissingFunction(error: {
305
+ code?: string;
306
+ message?: string;
307
+ } | null | undefined): boolean;
308
+
227
309
  /**
228
310
  * The support client: the app's own Supabase (migration 0006 from
229
311
  * `@marktiderman/genesis-supabase`) under row level security. Everything a
230
312
  * person does (send feedback, read their reports and the replies, reply, vote
231
313
  * on the idea board) and the Supabase sink for the feedback record.
314
+ *
315
+ * With a genesis-chat client (`chat` option) and the support-chat block's
316
+ * migration applied, a report's thread runs on genesis-chat: replies, reply,
317
+ * markRead and subscribe go through the report's conversation (live updates,
318
+ * files, internal notes for staff). Otherwise they use support_replies.
232
319
  */
233
320
 
234
321
  /** Any supabase-js client; the app's generated Database types don't need to list the support tables. */
@@ -262,6 +349,8 @@ interface SupportReport {
262
349
  staffReadAt: string | null;
263
350
  /** A staff reply the person hasn't read. */
264
351
  unread: boolean;
352
+ /** Who sent it (the person's own id, or for staff the reporter's). */
353
+ userId?: string | null;
265
354
  }
266
355
  interface SupportReply {
267
356
  id: string;
@@ -271,6 +360,17 @@ interface SupportReply {
271
360
  /** Written by the signed-in person. */
272
361
  mine: boolean;
273
362
  createdAt: string;
363
+ /** A staff-only internal note (support-chat block; staff only ever read them). Always false on support_replies. */
364
+ internal?: boolean;
365
+ /** Photos, files and voice notes (support-chat block). Empty on support_replies. */
366
+ attachments?: SupportReplyAttachment[];
367
+ /** Its number in the conversation (support-chat block), else null. */
368
+ seq?: number | null;
369
+ }
370
+ /** A file on a reply, with a signed URL when it is in storage. */
371
+ interface SupportReplyAttachment extends Omit<SupportChatAttachment, "url"> {
372
+ /** A signed URL (or the attachment's own https URL); null when it couldn't be signed. */
373
+ url: string | null;
274
374
  }
275
375
  interface SupportAttachmentView {
276
376
  id: string;
@@ -303,8 +403,26 @@ interface SupportClient extends FeedbackSink {
303
403
  }): Promise<SupportReport[]>;
304
404
  report(id: string): Promise<SupportReport | null>;
305
405
  replies(reportId: string): Promise<SupportReply[]>;
306
- reply(reportId: string, body: string): Promise<SupportReply>;
406
+ /** `internal`: a staff-only note (support-chat block, staff only). */
407
+ reply(reportId: string, body: string, options?: {
408
+ internal?: boolean;
409
+ }): Promise<SupportReply>;
307
410
  markRead(reportId: string): Promise<void>;
411
+ /**
412
+ * Where the report's thread lives: its genesis-chat conversation when a `chat` client was given
413
+ * and the support-chat block is installed, else support_replies.
414
+ */
415
+ thread(reportId: string): Promise<SupportThreadInfo>;
416
+ /** The report's genesis-chat conversation id, or null (no chat client, no block, or not readable). */
417
+ conversationId(reportId: string): Promise<string | null>;
418
+ /** Live updates for the thread (chat only): `onChange` runs on each hint and each (re)join. Returns the stop. */
419
+ subscribe(reportId: string, onChange: () => void): () => void;
420
+ /** Whether the signed-in person is support staff. */
421
+ isStaff(): Promise<boolean>;
422
+ /** Staff: the report's status, and the FeatureId it is about (null keeps it, "" clears it). */
423
+ setStatus(reportId: string, status: FeedbackStatus, featureId?: string | null): Promise<void>;
424
+ /** Staff (support-chat block): who has the report, a staff member or null. */
425
+ assign(reportId: string, userId: string | null): Promise<void>;
308
426
  /** Deletes the person's own report; its screenshots are queued for removal. */
309
427
  deleteReport(reportId: string): Promise<void>;
310
428
  attachments(reportId: string): Promise<SupportAttachmentView[]>;
@@ -323,6 +441,13 @@ interface SupportClient extends FeedbackSink {
323
441
  shippedForMe(): Promise<SupportIdea[]>;
324
442
  }
325
443
  interface SupportClientOptions {
444
+ /**
445
+ * A genesis-chat client (`createChatClient(supabase)`): with it, and the support-chat block's
446
+ * migration applied, a report's thread runs on genesis-chat. Without either, support_replies.
447
+ */
448
+ chat?: SupportChatClient | false;
449
+ /** Told once when a chat client was given but the block's migration isn't applied. Default `console.warn`. */
450
+ onChatUnavailable?: (message: string) => void;
326
451
  bucket?: string;
327
452
  signedUrlSeconds?: number;
328
453
  /** For the attachment file names. Default `crypto.randomUUID`. */
@@ -420,6 +545,20 @@ declare const supportCopy: {
420
545
  sending: string;
421
546
  notSent: string;
422
547
  retryHint: string;
548
+ /** A message's file, where the thread can't show it. */
549
+ voiceNote: string;
550
+ video: string;
551
+ file: string;
552
+ internalNote: string;
553
+ };
554
+ staff: {
555
+ status: string;
556
+ assignee: string;
557
+ unassigned: string;
558
+ reporter: string;
559
+ unavailableTitle: string;
560
+ unavailableBody: string;
561
+ failed: string;
423
562
  };
424
563
  status: {
425
564
  open: string;
@@ -474,4 +613,4 @@ declare function resolveSupportCopy(overrides?: SupportCopyOverrides): SupportCo
474
613
  /** Fills `{name}` placeholders. */
475
614
  declare function fill(template: string, values: Readonly<Record<string, string | number>>): string;
476
615
 
477
- export { isUnread as $, type SupportCopyOverrides as A, type BootConfig as B, CONTRACT_VERSION as C, DEVICE_MAX_BYTES as D, EXTRA_MAX_BYTES as E, type FeedbackReportSummary as F, SupportError as G, type SupportIdea as H, type IdeaStatus as I, type SupportReply as J, type SupportReport as K, LIMITS as L, type SupportSupabase as M, type WidgetConfig as N, type WidgetType as O, type WidgetTypeWire as P, attachmentBytes as Q, base64ToBytes as R, type SubmitFeedbackInput as S, type ThemeTokens as T, cleanRoute as U, composeCollectors as V, WIDGET_HEADERS as W, createContextCollector as X, createSupabaseSink as Y, createSupportClient as Z, fill as _, type SubmitFeedbackResult as a, resolveSupportCopy as a0, sanitizeContext as a1, supportCopy as a2, validateSubmitInput as a3, type FeedbackSink as b, type ContextCollector as c, type ContextCollectorOptions as d, FEEDBACK_REPORT_TYPES as e, FEEDBACK_STATUSES as f, FEEDBACK_TYPES as g, type FeedbackAttachment as h, type FeedbackContext as i, type FeedbackDelivery as j, type FeedbackOp as k, type FeedbackRecord as l, type FeedbackReportType as m, type FeedbackStatus as n, type FeedbackType as o, type FindingSeverity as p, type IdentityMode as q, type LauncherPosition as r, type Lazy as s, SUPPORT_BUCKET as t, type ScreenshotContentType as u, type SubmitValidation as v, type SupportAttachmentView as w, type SupportClient as x, type SupportClientOptions as y, type SupportCopy as z };
616
+ export { type WidgetTypeWire as $, type SupportAttachmentView as A, type BootConfig as B, CONTRACT_VERSION as C, DEVICE_MAX_BYTES as D, EXTRA_MAX_BYTES as E, type FeedbackReportSummary as F, type SupportChatAttachment as G, type SupportChatClient as H, type IdeaStatus as I, type SupportChatMessage as J, type SupportClient as K, LIMITS as L, type SupportClientOptions as M, type SupportCopy as N, type SupportCopyOverrides as O, SupportError as P, type SupportIdea as Q, type SupportReply as R, type SubmitFeedbackInput as S, type SupportReplyAttachment as T, type SupportReport as U, type SupportSupabase as V, type SupportThreadInfo as W, type ThemeTokens as X, WIDGET_HEADERS as Y, type WidgetConfig as Z, type WidgetType as _, type SubmitFeedbackResult as a, attachmentBytes as a0, base64ToBytes as a1, cleanRoute as a2, composeCollectors as a3, createContextCollector as a4, createSupabaseSink as a5, createSupportClient as a6, fill as a7, isMissingFunction as a8, isUnread as a9, resolveSupportCopy as aa, sanitizeContext as ab, supportCopy as ac, validateSubmitInput as ad, type FeedbackSink as b, type ContextCollector as c, type ContextCollectorOptions as d, FEEDBACK_REPORT_TYPES as e, FEEDBACK_STATUSES as f, FEEDBACK_TYPES as g, type FeedbackAttachment as h, type FeedbackContext as i, type FeedbackDelivery as j, type FeedbackOp as k, type FeedbackRecord as l, type FeedbackReportType as m, type FeedbackStatus as n, type FeedbackType as o, type FindingSeverity as p, type IdentityMode as q, type LauncherPosition as r, type Lazy as s, SUPPORT_BUCKET as t, SUPPORT_CHAT_BLOCK_ID as u, SUPPORT_CHAT_KIND as v, SUPPORT_CHAT_SUBJECT as w, SUPPORT_TO_CHAT_STATUS as x, type ScreenshotContentType as y, type SubmitValidation as z };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { B as BootConfig, S as SubmitFeedbackInput, a as SubmitFeedbackResult, F as FeedbackReportSummary, b as FeedbackSink } from './copy-CuTArBFg.js';
2
- export { C as CONTRACT_VERSION, c as ContextCollector, d as ContextCollectorOptions, D as DEVICE_MAX_BYTES, E as EXTRA_MAX_BYTES, e as FEEDBACK_REPORT_TYPES, f as FEEDBACK_STATUSES, g as FEEDBACK_TYPES, h as FeedbackAttachment, i as FeedbackContext, j as FeedbackDelivery, k as FeedbackOp, l as FeedbackRecord, m as FeedbackReportType, n as FeedbackStatus, o as FeedbackType, p as FindingSeverity, I as IdeaStatus, q as IdentityMode, L as LIMITS, r as LauncherPosition, s as Lazy, t as SUPPORT_BUCKET, u as ScreenshotContentType, v as SubmitValidation, w as SupportAttachmentView, x as SupportClient, y as SupportClientOptions, z as SupportCopy, A as SupportCopyOverrides, G as SupportError, H as SupportIdea, J as SupportReply, K as SupportReport, M as SupportSupabase, T as ThemeTokens, W as WIDGET_HEADERS, N as WidgetConfig, O as WidgetType, P as WidgetTypeWire, Q as attachmentBytes, R as base64ToBytes, U as cleanRoute, V as composeCollectors, X as createContextCollector, Y as createSupabaseSink, Z as createSupportClient, _ as fill, $ as isUnread, a0 as resolveSupportCopy, a1 as sanitizeContext, a2 as supportCopy, a3 as validateSubmitInput } from './copy-CuTArBFg.js';
3
- export { S as SUPPORT_BLOCK_ID, a as SupportBlockOptions, b as SupportPaths, d as defineSupportBlock, s as supportContributions, c as supportPaths } from './block-fZjTS2sY.js';
1
+ import { B as BootConfig, S as SubmitFeedbackInput, a as SubmitFeedbackResult, F as FeedbackReportSummary, b as FeedbackSink } from './copy-D3rc0SCl.js';
2
+ export { C as CONTRACT_VERSION, c as ContextCollector, d as ContextCollectorOptions, D as DEVICE_MAX_BYTES, E as EXTRA_MAX_BYTES, e as FEEDBACK_REPORT_TYPES, f as FEEDBACK_STATUSES, g as FEEDBACK_TYPES, h as FeedbackAttachment, i as FeedbackContext, j as FeedbackDelivery, k as FeedbackOp, l as FeedbackRecord, m as FeedbackReportType, n as FeedbackStatus, o as FeedbackType, p as FindingSeverity, I as IdeaStatus, q as IdentityMode, L as LIMITS, r as LauncherPosition, s as Lazy, t as SUPPORT_BUCKET, u as SUPPORT_CHAT_BLOCK_ID, v as SUPPORT_CHAT_KIND, w as SUPPORT_CHAT_SUBJECT, x as SUPPORT_TO_CHAT_STATUS, y as ScreenshotContentType, z as SubmitValidation, A as SupportAttachmentView, G as SupportChatAttachment, H as SupportChatClient, J as SupportChatMessage, K as SupportClient, M as SupportClientOptions, N as SupportCopy, O as SupportCopyOverrides, P as SupportError, Q as SupportIdea, R as SupportReply, T as SupportReplyAttachment, U as SupportReport, V as SupportSupabase, W as SupportThreadInfo, X as ThemeTokens, Y as WIDGET_HEADERS, Z as WidgetConfig, _ as WidgetType, $ as WidgetTypeWire, a0 as attachmentBytes, a1 as base64ToBytes, a2 as cleanRoute, a3 as composeCollectors, a4 as createContextCollector, a5 as createSupabaseSink, a6 as createSupportClient, a7 as fill, a8 as isMissingFunction, a9 as isUnread, aa as resolveSupportCopy, ab as sanitizeContext, ac as supportCopy, ad as validateSubmitInput } from './copy-D3rc0SCl.js';
3
+ export { S as SUPPORT_BLOCK_ID, a as SupportBlockOptions, b as SupportPaths, d as defineSupportBlock, c as defineSupportChatBlock, s as supportContributions, e as supportPaths } from './block-ClKVvk-d.js';
4
4
  import '@supabase/supabase-js';
5
5
  import '@marktiderman/genesis-features/blocks';
6
6
 
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- export { SUPPORT_BLOCK_ID, SUPPORT_BUCKET, SupportError, createSupabaseSink, createSupportClient, defineSupportBlock, isUnread, supportContributions, supportPaths } from './chunk-4VFYVQUF.js';
2
- import { WIDGET_HEADERS, CONTRACT_VERSION } from './chunk-ZV4KJBUF.js';
3
- export { CONTRACT_VERSION, DEVICE_MAX_BYTES, EXTRA_MAX_BYTES, FEEDBACK_REPORT_TYPES, FEEDBACK_STATUSES, FEEDBACK_TYPES, LIMITS, WIDGET_HEADERS, attachmentBytes, base64ToBytes, cleanRoute, composeCollectors, createContextCollector, fill, resolveSupportCopy, sanitizeContext, supportCopy, validateSubmitInput } from './chunk-ZV4KJBUF.js';
1
+ export { SUPPORT_BLOCK_ID, SUPPORT_BUCKET, SUPPORT_CHAT_BLOCK_ID, SUPPORT_CHAT_KIND, SUPPORT_CHAT_SUBJECT, SUPPORT_TO_CHAT_STATUS, SupportError, createSupabaseSink, createSupportClient, defineSupportBlock, defineSupportChatBlock, isMissingFunction, isUnread, supportContributions, supportPaths } from './chunk-QNUAKW7K.js';
2
+ export { DEVICE_MAX_BYTES, EXTRA_MAX_BYTES, FEEDBACK_TYPES, attachmentBytes, base64ToBytes, composeCollectors, createContextCollector, sanitizeContext } from './chunk-MMBNG2NK.js';
3
+ import { WIDGET_HEADERS, CONTRACT_VERSION } from './chunk-OSB6G6Y3.js';
4
+ export { CONTRACT_VERSION, FEEDBACK_REPORT_TYPES, FEEDBACK_STATUSES, LIMITS, WIDGET_HEADERS, cleanRoute, fill, resolveSupportCopy, supportCopy, validateSubmitInput } from './chunk-OSB6G6Y3.js';
4
5
 
5
6
  // src/intake.ts
6
7
  var IntakeError = class extends Error {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/intake.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The HTTP client for the `genesis-support-intake` Edge Function: the wire\n * contract (boot, submit, list) for code that doesn't hold a supabase-js\n * client, such as a script-tag web loader or another backend. Zero\n * dependencies, global `fetch`.\n */\nimport {\n CONTRACT_VERSION,\n WIDGET_HEADERS,\n type BootConfig,\n type FeedbackOp,\n type FeedbackReportSummary,\n type SubmitFeedbackInput,\n type SubmitFeedbackResult,\n} from \"./contract\";\nimport type { FeedbackSink } from \"./record\";\n\nexport interface IntakeClientConfig {\n /** The app's Supabase URL (`https://<project>.supabase.co`, or the local one). */\n endpoint: string;\n /** The publishable key, sent as `apikey` and `x-genesis-key` (an identifier, not a secret). */\n publishableKey: string;\n /** The signed-in person's access token, read per request. */\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n /** The function's name, if you deployed it under another. */\n functionName?: string;\n fetchImpl?: typeof fetch;\n}\n\n/** A non-2xx answer (`status` 0: it never arrived). `code` is the function's `error`. */\nexport class IntakeError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"IntakeError\";\n }\n}\n\nexport interface IntakeClient {\n boot(signal?: AbortSignal): Promise<BootConfig>;\n submit(input: SubmitFeedbackInput, signal?: AbortSignal): Promise<SubmitFeedbackResult>;\n list(signal?: AbortSignal): Promise<FeedbackReportSummary[]>;\n}\n\nexport function createIntakeClient(config: IntakeClientConfig): IntakeClient {\n const url = `${config.endpoint.replace(/\\/$/, \"\")}/functions/v1/${config.functionName ?? \"genesis-support-intake\"}`;\n const f = config.fetchImpl ?? fetch;\n\n async function headers(): Promise<Record<string, string>> {\n const token = await config.getAccessToken?.();\n return {\n apikey: config.publishableKey,\n [WIDGET_HEADERS.key]: config.publishableKey,\n [WIDGET_HEADERS.contract]: String(CONTRACT_VERSION),\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n };\n }\n\n async function send<T>(init: RequestInit): Promise<T> {\n let res: Response;\n try {\n res = await f(url, init);\n } catch (error) {\n throw new IntakeError(error instanceof Error ? error.message : \"network request failed\", 0, \"transport_error\");\n }\n if (!res.ok) {\n let code: string | undefined;\n try {\n code = ((await res.json()) as { error?: string }).error;\n } catch {\n // not JSON\n }\n throw new IntakeError(code ?? `${res.status} ${res.statusText}`, res.status, code);\n }\n return (await res.json()) as T;\n }\n\n const post = async <T,>(op: FeedbackOp, args: object, signal?: AbortSignal) =>\n // `op` last, so an `op` in args can never pick the operation.\n send<T>({ method: \"POST\", headers: { ...(await headers()), \"content-type\": \"application/json\" }, body: JSON.stringify({ ...args, op }), signal });\n\n return {\n boot: async (signal) => send<BootConfig>({ method: \"GET\", headers: await headers(), signal }),\n submit: (input, signal) => post<SubmitFeedbackResult>(\"submit\", input, signal),\n list: (signal) => post<FeedbackReportSummary[]>(\"list\", {}, signal),\n };\n}\n\n/**\n * A sink over the intake function. The function takes no screenshots (they\n * go to storage with a session), so attachments are reported as dropped.\n */\nexport function createIntakeSink(client: IntakeClient): FeedbackSink {\n return {\n async submit(record) {\n const result = await client.submit({\n type: record.type,\n title: record.title,\n body: record.message,\n route: record.context.route,\n appVersion: record.context.appVersion,\n envType: record.context.platform,\n context: record.context.extra,\n });\n return { id: result.reportId, attachmentsDropped: record.attachments?.length ?? 0 };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/intake.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The HTTP client for the `genesis-support-intake` Edge Function: the wire\n * contract (boot, submit, list) for code that doesn't hold a supabase-js\n * client, such as a script-tag web loader or another backend. Zero\n * dependencies, global `fetch`.\n */\nimport {\n CONTRACT_VERSION,\n WIDGET_HEADERS,\n type BootConfig,\n type FeedbackOp,\n type FeedbackReportSummary,\n type SubmitFeedbackInput,\n type SubmitFeedbackResult,\n} from \"./contract\";\nimport type { FeedbackSink } from \"./record\";\n\nexport interface IntakeClientConfig {\n /** The app's Supabase URL (`https://<project>.supabase.co`, or the local one). */\n endpoint: string;\n /** The publishable key, sent as `apikey` and `x-genesis-key` (an identifier, not a secret). */\n publishableKey: string;\n /** The signed-in person's access token, read per request. */\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n /** The function's name, if you deployed it under another. */\n functionName?: string;\n fetchImpl?: typeof fetch;\n}\n\n/** A non-2xx answer (`status` 0: it never arrived). `code` is the function's `error`. */\nexport class IntakeError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"IntakeError\";\n }\n}\n\nexport interface IntakeClient {\n boot(signal?: AbortSignal): Promise<BootConfig>;\n submit(input: SubmitFeedbackInput, signal?: AbortSignal): Promise<SubmitFeedbackResult>;\n list(signal?: AbortSignal): Promise<FeedbackReportSummary[]>;\n}\n\nexport function createIntakeClient(config: IntakeClientConfig): IntakeClient {\n const url = `${config.endpoint.replace(/\\/$/, \"\")}/functions/v1/${config.functionName ?? \"genesis-support-intake\"}`;\n const f = config.fetchImpl ?? fetch;\n\n async function headers(): Promise<Record<string, string>> {\n const token = await config.getAccessToken?.();\n return {\n apikey: config.publishableKey,\n [WIDGET_HEADERS.key]: config.publishableKey,\n [WIDGET_HEADERS.contract]: String(CONTRACT_VERSION),\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n };\n }\n\n async function send<T>(init: RequestInit): Promise<T> {\n let res: Response;\n try {\n res = await f(url, init);\n } catch (error) {\n throw new IntakeError(error instanceof Error ? error.message : \"network request failed\", 0, \"transport_error\");\n }\n if (!res.ok) {\n let code: string | undefined;\n try {\n code = ((await res.json()) as { error?: string }).error;\n } catch {\n // not JSON\n }\n throw new IntakeError(code ?? `${res.status} ${res.statusText}`, res.status, code);\n }\n return (await res.json()) as T;\n }\n\n const post = async <T,>(op: FeedbackOp, args: object, signal?: AbortSignal) =>\n // `op` last, so an `op` in args can never pick the operation.\n send<T>({ method: \"POST\", headers: { ...(await headers()), \"content-type\": \"application/json\" }, body: JSON.stringify({ ...args, op }), signal });\n\n return {\n boot: async (signal) => send<BootConfig>({ method: \"GET\", headers: await headers(), signal }),\n submit: (input, signal) => post<SubmitFeedbackResult>(\"submit\", input, signal),\n list: (signal) => post<FeedbackReportSummary[]>(\"list\", {}, signal),\n };\n}\n\n/**\n * A sink over the intake function. The function takes no screenshots (they\n * go to storage with a session), so attachments are reported as dropped.\n */\nexport function createIntakeSink(client: IntakeClient): FeedbackSink {\n return {\n async submit(record) {\n const result = await client.submit({\n type: record.type,\n title: record.title,\n body: record.message,\n route: record.context.route,\n appVersion: record.context.appVersion,\n envType: record.context.platform,\n context: record.context.extra,\n });\n return { id: result.reportId, attachmentsDropped: record.attachments?.length ?? 0 };\n },\n };\n}\n"]}
package/dist/native.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentType, ReactNode } from 'react';
3
3
  import { NativeBlock, NativeBlockRootProps } from '@marktiderman/genesis-native';
4
- import { S as SUPPORT_BLOCK_ID, b as SupportPaths } from './block-fZjTS2sY.js';
5
- import { u as ScreenshotContentType, M as SupportSupabase, c as ContextCollector, A as SupportCopyOverrides, x as SupportClient, z as SupportCopy, o as FeedbackType, j as FeedbackDelivery, H as SupportIdea, K as SupportReport } from './copy-CuTArBFg.js';
4
+ import { S as SUPPORT_BLOCK_ID, b as SupportPaths } from './block-ClKVvk-d.js';
5
+ import { y as ScreenshotContentType, V as SupportSupabase, H as SupportChatClient, c as ContextCollector, O as SupportCopyOverrides, K as SupportClient, N as SupportCopy, o as FeedbackType, j as FeedbackDelivery, Q as SupportIdea, W as SupportThreadInfo, U as SupportReport } from './copy-D3rc0SCl.js';
6
6
  import { InboxPageProps, ThreadPageProps } from '@marktiderman/genesis-ui-native/layouts';
7
7
  import '@marktiderman/genesis-features/blocks';
8
8
  import '@supabase/supabase-js';
@@ -70,6 +70,11 @@ interface PendingFeedback {
70
70
  interface SupportOptions {
71
71
  /** The app's Supabase client (migration 0006 applied). */
72
72
  supabase: SupportSupabase;
73
+ /**
74
+ * genesis-chat's client (`createChatClient(supabase)`): with the support-chat block's migration
75
+ * applied, a report's thread runs on genesis-chat (live, with files). Without it, support_replies.
76
+ */
77
+ chat?: SupportChatClient;
73
78
  /** Opens a path (expo-router's `router.push`). */
74
79
  navigate: (href: string) => void;
75
80
  /** `Platform.OS`. Default from React Native. */
@@ -177,8 +182,27 @@ type ThreadProps = Pick<ThreadPageProps, "messages" | "composer" | "closedNotice
177
182
  loading: boolean;
178
183
  /** Read, and there is no such report (deleted, or not the person's): leave the screen. */
179
184
  notFound: boolean;
185
+ /** `chat`: the thread runs on genesis-chat (the support-chat block), live; `replies`: support_replies, re-read every `pollMs`. */
186
+ mode: SupportThreadInfo["mode"] | null;
187
+ /** The report's genesis-chat conversation, for a screen that renders it with genesis-chat's own native parts. */
188
+ conversationId: string | null;
180
189
  };
181
- /** One report as a conversation: the report itself, then the replies. Marks it read as it opens and as replies arrive. */
190
+ /**
191
+ * Where a report's thread lives: its genesis-chat conversation (the support-chat block, with a
192
+ * `chat` client given to `createSupport`), or support_replies. For a screen of your own that
193
+ * renders the conversation with `@marktiderman/genesis-chat/native` (ChatThreadScreen).
194
+ */
195
+ declare function useSupportConversation(reportId: string): {
196
+ mode: SupportThreadInfo["mode"] | null;
197
+ conversationId: string | null;
198
+ loading: boolean;
199
+ error: boolean;
200
+ };
201
+ /**
202
+ * One report as a conversation: the report itself, then the replies. Marks it read as it opens
203
+ * and as replies arrive. On genesis-chat (the support-chat block) it updates live and shows the
204
+ * photos in replies; otherwise it re-reads support_replies every `pollMs`.
205
+ */
182
206
  declare function useSupportThread(reportId: string, options?: {
183
207
  pollMs?: number;
184
208
  }): ThreadProps;
@@ -204,4 +228,4 @@ declare function SendFeedbackButton({ testID, screenshot }: {
204
228
  screenshot?: boolean;
205
229
  }): react.JSX.Element | null;
206
230
 
207
- export { type AccelerometerLike, type AccelerometerMeasurement, type CaptureOptions, FeedbackForm, type FeedbackFormProps, type PendingFeedback, type PendingScreenshot, SUPPORT_QUERY_ROOT, ScreenshotAnnotator, type ScreenshotAnnotatorProps, SendFeedbackButton, type ShakeOptions, type SupportBlock, type SupportOptions, SupportProvider, type ViewShotLike, VoteButton, boxOf, createSupport, listenForShake, strokeDots, useIdeaBoard, useOptionalSupport, usePendingFeedback, useShippedIdeas, useSupport, useSupportInbox, useSupportThread, useUnreadReplies };
231
+ export { type AccelerometerLike, type AccelerometerMeasurement, type CaptureOptions, FeedbackForm, type FeedbackFormProps, type PendingFeedback, type PendingScreenshot, SUPPORT_QUERY_ROOT, ScreenshotAnnotator, type ScreenshotAnnotatorProps, SendFeedbackButton, type ShakeOptions, type SupportBlock, type SupportOptions, SupportProvider, type ViewShotLike, VoteButton, boxOf, createSupport, listenForShake, strokeDots, useIdeaBoard, useOptionalSupport, usePendingFeedback, useShippedIdeas, useSupport, useSupportConversation, useSupportInbox, useSupportThread, useUnreadReplies };
package/dist/native.js CHANGED
@@ -1,5 +1,6 @@
1
- import { supportPaths, createSupportClient, defineSupportBlock, SupportError } from './chunk-4VFYVQUF.js';
2
- import { resolveSupportCopy, createContextCollector, composeCollectors, fill, FEEDBACK_TYPES } from './chunk-ZV4KJBUF.js';
1
+ import { supportPaths, createSupportClient, defineSupportBlock, SupportError } from './chunk-QNUAKW7K.js';
2
+ import { createContextCollector, composeCollectors, FEEDBACK_TYPES } from './chunk-MMBNG2NK.js';
3
+ import { resolveSupportCopy, fill } from './chunk-OSB6G6Y3.js';
3
4
  import { createContext, useContext, useSyncExternalStore, useRef, useState, useEffect, useMemo } from 'react';
4
5
  import { Platform, Dimensions, PanResponder, View, Image, Modal, Alert } from 'react-native';
5
6
  import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
@@ -50,7 +51,7 @@ function useUnreadReplies() {
50
51
  function createSupport(options) {
51
52
  const copy = resolveSupportCopy(options.copy);
52
53
  const paths = supportPaths(options.basePath);
53
- const client = createSupportClient(options.supabase);
54
+ const client = createSupportClient(options.supabase, { chat: options.chat });
54
55
  const platform = options.platform ?? Platform.OS;
55
56
  let route = null;
56
57
  let pending = null;
@@ -511,14 +512,31 @@ function firstLine(text) {
511
512
  const line = text.split("\n").find((l) => l.trim()) ?? "";
512
513
  return line.length > 120 ? `${line.slice(0, 119)}\u2026` : line;
513
514
  }
515
+ function useSupportConversation(reportId) {
516
+ const { client } = useSupport();
517
+ const query = useQuery({ queryKey: key("thread", reportId), queryFn: () => client.thread(reportId), staleTime: Infinity });
518
+ return { mode: query.data?.mode ?? null, conversationId: query.data?.conversationId ?? null, loading: query.isPending, error: query.isError };
519
+ }
520
+ function fileLabels(reply, copy) {
521
+ return (reply.attachments ?? []).filter((a) => a.kind !== "image").map((a) => a.kind === "voice" ? copy.thread.voiceNote : a.kind === "video" ? copy.thread.video : a.name || copy.thread.file);
522
+ }
514
523
  var CLOSED = /* @__PURE__ */ new Set(["addressed", "wont_fix"]);
515
524
  function useSupportThread(reportId, options = {}) {
516
525
  const block = useSupport();
517
526
  const { copy, client } = block;
518
527
  const queryClient = useQueryClient();
519
- const poll = options.pollMs ?? 3e4;
528
+ const where = useQuery({ queryKey: key("thread", reportId), queryFn: () => client.thread(reportId), staleTime: Infinity });
529
+ const live = where.data?.mode === "chat";
530
+ const poll = live ? false : options.pollMs ?? 3e4;
520
531
  const report = useQuery({ queryKey: key("report", reportId), queryFn: () => client.report(reportId), refetchInterval: poll });
521
- const replies = useQuery({ queryKey: key("replies", reportId), queryFn: () => client.replies(reportId), refetchInterval: poll });
532
+ const replies = useQuery({ queryKey: key("replies", reportId), queryFn: () => client.replies(reportId), refetchInterval: poll, enabled: where.isFetched });
533
+ useEffect(() => {
534
+ if (!live) return;
535
+ return client.subscribe(reportId, () => {
536
+ void queryClient.invalidateQueries({ queryKey: key("replies", reportId) });
537
+ void queryClient.invalidateQueries({ queryKey: key("report", reportId) });
538
+ });
539
+ }, [client, reportId, live, queryClient]);
522
540
  const attachments = useQuery({ queryKey: key("attachments", reportId), queryFn: () => client.attachments(reportId), staleTime: 50 * 6e4 });
523
541
  const [draft, setDraft] = useState("");
524
542
  const [failed, setFailed] = useState([]);
@@ -545,12 +563,13 @@ function useSupportThread(reportId, options = {}) {
545
563
  timestamp: r.createdAt,
546
564
  attachments: (attachments.data ?? []).filter((a) => a.url).map((a) => ({ id: a.id, uri: a.url, width: a.width ?? void 0, height: a.height ?? void 0, accessibilityLabel: copy.thread.screenshot }))
547
565
  };
548
- const rest = (replies.data ?? []).map((reply) => ({
566
+ const rest = (replies.data ?? []).filter((reply) => !reply.internal).map((reply) => ({
549
567
  id: reply.id,
550
- body: reply.body,
568
+ body: [reply.body, ...fileLabels(reply, copy)].filter((part) => part.trim()).join("\n"),
551
569
  mine: reply.mine,
552
570
  authorName: reply.fromStaff ? copy.thread.support : void 0,
553
- timestamp: reply.createdAt
571
+ timestamp: reply.createdAt,
572
+ attachments: (reply.attachments ?? []).filter((a) => a.kind === "image" && a.url).map((a) => ({ id: a.id, uri: a.url, width: a.width ?? void 0, height: a.height ?? void 0, accessibilityLabel: copy.thread.screenshot }))
554
573
  }));
555
574
  const pending = send.isPending && send.variables ? [{ id: "sending", body: send.variables, mine: true, timestamp: (/* @__PURE__ */ new Date()).toISOString(), status: "sending" }] : [];
556
575
  const failedMessages = failed.map((f) => ({
@@ -573,6 +592,8 @@ function useSupportThread(reportId, options = {}) {
573
592
  report: r,
574
593
  loading: report.isPending,
575
594
  notFound: report.isSuccess && r === null,
595
+ mode: where.data?.mode ?? null,
596
+ conversationId: where.data?.conversationId ?? null,
576
597
  labels: { you: copy.thread.you, sending: copy.thread.sending, notSent: copy.thread.notSent, retryHint: copy.thread.retryHint },
577
598
  messages,
578
599
  readAt: r?.staffReadAt ?? null,
@@ -717,6 +738,6 @@ function SendFeedbackButton({ testID = "genesis-support-send-feedback-button", s
717
738
  return /* @__PURE__ */ jsx(NativeButton, { variant: "outline", onPress: () => void block.openFeedback({ screenshot }), testID, children: block.copy.settings.sendFeedback });
718
739
  }
719
740
 
720
- export { FeedbackForm, SUPPORT_QUERY_ROOT, ScreenshotAnnotator, SendFeedbackButton, SupportProvider, VoteButton, boxOf, createSupport, listenForShake, strokeDots, useIdeaBoard, useOptionalSupport, usePendingFeedback, useShippedIdeas, useSupport, useSupportInbox, useSupportThread, useUnreadReplies };
741
+ export { FeedbackForm, SUPPORT_QUERY_ROOT, ScreenshotAnnotator, SendFeedbackButton, SupportProvider, VoteButton, boxOf, createSupport, listenForShake, strokeDots, useIdeaBoard, useOptionalSupport, usePendingFeedback, useShippedIdeas, useSupport, useSupportConversation, useSupportInbox, useSupportThread, useUnreadReplies };
721
742
  //# sourceMappingURL=native.js.map
722
743
  //# sourceMappingURL=native.js.map