Skip to content
← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

FollowFork

src/starter-tanstack-templates.ts

65 lines · 138.4 KB · TypeScript
/**
 * Embedded copy of `starter-tanstack/` (excluding STARTER.md and binary icons) for the Worker scaffolder.
 * Source of truth is the `starter-tanstack/` directory — regenerate via `node scripts/embed-starter.mjs` when it changes.
 * Icons: scaffolded apps use kit `iconPngResponse` (no binary copy required).
 */
export const TANSTACK_STARTER_FILES: Record<string, string> = {
	".gitignore": "node_modules/\n.kody-dev/\n.wrangler/\n*.log\n.DS_Store\n.env\n.env.*\n",
	".kody/icon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 128 128\" width=\"128\" height=\"128\" role=\"img\" aria-label=\"TanStack Kody mark\">\n  <image href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAYFBMVEX69vXv5+Tk2dXXzMnMwsDBt7e5rq2wqKmmoqSnm5mimZudlZeYj5CUiYmPh4qMg4WJf3+Eenxzd3t6bm1uZ2hjWlpjRjczVFo4QUM5NDQnKiwiHBwJCQkAAQEAAAEAAACDXMOvAAAay0lEQVR42r1biWKjsJLENiCBBJIiRMTh8f//5Va1wHbmerNHlkkcT+ygUp/VrXb1eDzu9wevfd/Xdd23dZP/3u/H77/3qh5c5b4tOfvRKq1H25u0rtvOF/4fIFRcPXvdNg2+VKualk+VcnFaNkL48c0A7tvUc8m2qQmgaQRJ+WnT9vhuMVRRt62sXte3243fNzyr61qQ6LDu3wuh4l6PhfkDK+PhesVzgKJWYt6/E0HVnGvXx64VHupTFlCNUmH7RgRVfa5OM6AyxCDaggIX7cIm8YnvAQBp15Q2ltVK9b2G1JXS2LhAuNaCBwi+TQIUvNI9bFHDB7XpdTf2QEKXrBvKgPj8sj9+fI8NYPl+GN04GG0Unlur3ajsKEKAe9A7KIb0PZGxwu61scaHENwYrO6hhX40xNH2WnWqKUbSqvQtdlC1qtda9zaMdhhD9IMxEIVVwzj0djC2LwHiBu3k75BBZQcsA41D+eM4BArCBWe1tlBKb6yu6yIDiCrd/++FUFnjYozeO6zsIQSsaz1U0SrjrNGDhRkwKOFb629wx8raEFOaoh+hBIe1w6CVtZC9gVHo3sEd6laMEUFq2v/PAUwT9m+Zia2D8EOcooMZwC6t7S2UgycwEngEtWDy/04E93I9n9wflQ/YpsKeVe8DlOEcgEAYxgyh1wQBIxkQm5ggWmX/F2H5zYafgrxXLjo7jrCDID9HbL23o3cQgB3gkUb3g9jIMNBF7bT/zzJ0+ZN92/OSqO+Pj2lZkGqrkBINP05TijRDLAwE3hsz8ndpwdvWdXleOZMt8Y7/7eXXNAU4OLOd0A1EgJArGGBKUa4wxjgg92glKJZtAz/ENx/kAmvctiWleREM/w7hzr0ng3srJcQLXy1iPUyrwsLAANtLlAAFAHeACWi7rTM3jw3fufAbiDUDwz9D4O63HI0G72OyZ3BvSvpDtK+cj4kYUk6UBGIhccAuw7YQwILlKQW5lvz5+Ukd7CuUs/0GwmnlX3e/jrDiwrsY1Mh3Cglpmiq4SCVg/SlnBgRrgh+QCZKsmlfZ/Cp6gCkAQEqfn+u+QxV5e7PvH78iKevvOWhKXrje7UW9BMqtGqF4AoBmE9eHEKgNFzNXXdIhfAKgLVIEBPGZ9x3AM1Wyvxbdz+sFbHGqLC80rxHahUfhPfhNZePEUChXAZLzsqSQVgLI+QRQUCyf5zV9LDskxnfRM8Q+8SydV4ahQij3bIVkFXnD7Kh3DXVAyULEK/garGDihd3T8eac8xzl1tuhAUFw2sCx/sdH3lOSVScfsOBK5MVjBAl9eMsKuaxQLkUzVLpTGhmXaQ5uoNpqCvhH6/MSCGNBkHySRV8ARAD5BJCwvndbFgAztYb1l+1+309fwbNFNKohe1Y8oDo9QAxMvIh2SPqkHi2MMODPxe6cIDj0Ae2ueSOMNwinBLj/j7HPAiAnwoa53r+YiTjLBtlG5DODZbVziDyBYY68h5aB72qi52H5ruvs4E8AmX4+FwBv11pungSAsznJ6wuiCLDs774qUsIu7vsyQcOB+w7RWj+OWG2wjIgwBq2ZDQNXB/XtuoFqKEpY7ztUmNb35WmVTwHggieu9xURE9pOxWtK4C5ikrd97o/sA+WgrEOEG7FJ5yF3j0dvexhhGLi6ACCCQBkw2OK2sIGv6y/vACYoefVGN5DUmtbjHSfKIqYP6/Jj5b5SHCCDsefiMYDsOUfqiVDsu7bUokp1yICuxEUJ+tuy7F/WX54mSC/AtpMChW7wgsiquO6bmj6wkJnuiy/m7f1oEWc9jIB+CNbTV24AGS9lmcigoyHQgQgApv0FwCFcubn9YDiGCXunYYJJQtXpq8fyH6OFyZvPO2INkvoSkWgCuBZ4Tt8bJB3nK9e1LIEKgFZMEYFpZqzNkEPe97f1j93J3c3nkjY6O/QLXaX1XUwnACQ1sP5xTz5iY0sYoHHYAYkn3c47Vw2lBLu9iUAA3DemBjieGPevEhghgLyeYXxbpvXEmV8W+CHrG7Nm6N3FVSI96dbgoQ3IwRgAqI/q+BQBoE7z9gM2OPllgzkWBPJYECTeHgrYJu6LEJA0lqeo8tNORwLAtc4Ic4kRPkiuRelD/o8CZHgDQNbbdrazuKfYIN4Qdga4dys47g+tT+s0OOwmSMxNT2sROR0AuDq8fU0+4DbrVKgfAKAUQg0AilfBB+snAIrAdkjQuCEiHd6I8LIsXwGswsug9MS8MmjjmBXPvHk6KxH4QwB2m9xyx0sTrI7KgAEY41D7jL6yXXOTEp2XiIA2kJPrwZtQMyzPlTf5kvvDPJZsmVpbbibEedvOQMDXgWB62aD+2MK0UH9g/D0johsQkqzDa15UIB2Zo0nR0gZACJCrmtaMLkjKZRuPRplp894hrwQrfRQwSIMMM63IzftLRgcA1lYAkLNbphmynOCDPUuxgDAEjzCqPyRQRFDM0PppTj2TtUKwIHGzYwzQWUkqpBNKDKap+WeK9+wniCCv6zsCQqCdabPGaYmwEaQFo0c3+CmVgDs5ekEBcHuTACI7EmV9tGqQx0kgkLyY2sEsdenm6FY6By3CCuIa9j8t20sNEEEJF0al7OEyAiCwFA2kIFOa5hn5uuqwpcISDwSIAxNko6Rzodk4QeBgE4X5k6V86WQ2Ta/ZO6ghFchZdXFjQnozxCQQvLYL2E0BAN+DP4aScCaSwKkiQa6vr1CgOo/MpUu7ZrSozrBAgBzBHpBJtbBb4biIMLCfuu0tyijTNiYvc36GLCEGGQiQtKctnwAQuSLFgAwu7CtVbf3kqpQAinJESwpdtVAXzbU3sAWG707Tf4ThCrPDzpnEtAXLMOwqRtQL+QxapY4Ce01xS4gsCFQLwiCCobOkHkd+qiy54QtAa1ke99Iws0zZIC9WN7Dl4EfEFBYu0YJQ9paVDa2j91OeehgGZUYPhr8s+emS3D/yDiWQyf2i65B2AcHR3ebKnACENcMCYKEON9cIB4haIy7Uzw6R2wxI36Ry/FuEHw0bMWxkIA7BaElv7NAh36VQMgRgoNpCnhg6ZfATANgK6AAammbew/uQDdVLB7QApgsWwqxNo5gLBY9KuVEjGxciE2R0Z+imPap6AEBQRi2jGWVxcz3iL8TPIItt8STA/YhoCfO3XVs6oiRgsPdqHEz7ygZwAe8ia3QLM42h1IzIOTBB1Q+iGrA5qN0iGOE/WnkJTzDtHtpA+re62AheIoKEUkscR7kFcaATIy7FcYecHCrwsvZZMLUIUn7w5Cyy9sQvqRcirNFZaaCRTUEiGgQTgUoH6pyBxZFkwLrItmEzbO1IqO7FpfF0TGR/TVsfURdqMAQgjKiUiiYyVIxcHNWClDkTfkXiDtOw0jExeD0GPVikVPZVAnsIYFt0Rusi61q6qWUUZixRLEeUYqYZh66TLnQtnUcgsGBEHtsqv2rbAR4Au3NSHk5iRVjaD6IJuIGhKTowWkTmEXblIJQgBVACJNBt2T1uDPcoHU8rYbRlw5OxXZV2V31m385VuKUbiYB4pF/l2S/BzmlC1CG1Ecjt5UU4BSp6B2MfhoTcyqCKEJyiQcJAGQwfwZsMGxBYGs5rDjnQ2eRg5i3qtGqo/MgE3UkSogGMTkqlUrLjYvuGAZSVmzhm4JrDMDnkRHIXSgoVy4TiCsJBCMENB+YsRZOgItj3blXpN7bN7XlJ3KtGyBOLdhAtqLslWweAScp1SMAoKaEY/qVqYAAbCAIABk0KP1FVrAMZQj23gAIEPsLAACyul1MPSR/PrsD15fds0UzcIOQ6TQ4OP0qJKNtf03GSdHSLW4lg3hEIOzpGkE6sIkAKqTf4MNgXiwF6CiXAhA4lIJPSPeVO1+uJAIqvClGV1IAowQ5RYLlYmia34+3lT+QUCYJQpK4srMaBfIUAGP/nhEIb9Cy6ACIDLyH34FEkT0FqdnrF/q+vG0ICXcW2EHl4mrE+uSJUIgEApOvtzdeTtCBj3qTJAf0F0HsntST7Agu9NgoAh9Dt2AMfHNkKwqXQGzmCub4hgOFV8dwvlGjpOSIDmNaU1fV6uRziOv7iy/+a0SMyH9W85J08S5cHIRyBAXGDKJA0mbaOs8k3CVx5IAUALnzQ5yDAkYxRKycSmNLI9S/Y9+1S4UnFhwufXvkC/nsNdJO8yNqkynM604e47WQcwxNpHNVQS3/oTaVkM7YSR6YEIpvl5LgO2YUyqcv76uulHqCPqceicl0OAAH5TaAuhbQiVlmmWjjUwEKfuQ+lYBiRxSEHCcFfAEAtQ/UhlgzwDLI0GCOOPmV9rn+L0nN5PDZ3ORHgX524cZ41sI6gFAJKDKkyFVHAA4LkUoYlBkQlpgP9iQzl3i1CcTialIHlCusVlowQQDiY6q1FebbN8rXNtwIAt2iTEB4IgPmTNhCF4F8LtWOqYbXPvURag+KhXFHBCeB6UyFWR0smGTkUMKMcncSigHN9XsPlgvKjIMAN6riQ9SUpjoPU81NzI7Inv6XLamWk98XzCETnooJLQQANgH0UAHnxLXOGtgzyAaTG4n2w82stq297xrq3OK9JANxuPkvn/OgvTtIX1M9bH3VOEUXbIcQiazGcFi843tV0DrQ80GDTVEg3CyZkA3C1mm9q6irI8vt9wbpNGuZFcf26SwXA/GpM5iVeLk/pvlwWMBTD5qhLI+QpAgAAsoqHFVPy7BmC0AilYL4vKrpduX9WnbuvroNScY5QQN1NPwNgdzPdqjcE70AcG5HM+oym1/IWhOEBv62MYxIyrZAohi3Vjz4oeUvXKNqelL37slyrBlZA6FHWp+vNLwlk2smB4PK66LI3xOgwChUBiCO8taCNIVTsm03MWD25E2guaFL0ArPpmgAyv59dGnVhWxDhtEvvACYpMUjFkwhXQlZ1uosEr6pPyE0kg2xXXyW8IQpCAiMAjB9BkbwVP+TpcQgFQHtz+zxLD/zowOK7vTXH+oKAfJSxfMavZlrO5TbEGWDM9QWhusSj2MFCN7FvxZyKiFch/3lDt21J+mmJZKTiUHV77UQF5dr2ByLC1tyGhV3p5TACcBTp07DH3l5uVzufByvhcoqhqtroTX/MikgaEvlP9AKUnlrYEZk3H0ekh2sBcGtgg6ipDgh3GOR8a3JpiBYBgLJZIz0NtoBu9VzCxoygsT/jFr7j2Ou+nFeRn6ICTX5EhKi2CZVXOUDSQqHkyLaWNNQ21xIFzi4RnoabXd8BgMaCf3gBkHOdyvr+UnXzM25REgYS0FoWR3HbgVKD/0B51WMFAPaNYf4QxtCz4tOtGEqransgkB4No3Grpk1UUADMsG/SqCjnHFnWxzeXjBBCPBNYVZPVl3p+HAYeSoYICbjqvhgl0xtIWaSzlr18Ni1oqaqrJ0HARpVow7eezcNlPQEktv1wtxIZ+CZaLA0Q5jFv7QngygqJPY1asfpB6uUxZfDVIxvp28P2wG8MkjFSpz4OeWoYTZqf17bH1vv8EwAARvyeZrHMorAdERsBZvDLdAKAH1ih5rfGMYFZjXg3HQAgAVRe3jFrkxQxJIi45JQpnMujzGyHEHhw8gQgRBIMFHXUIgCOsIXAXFXDXJQh1xi1sMJbgwIcLNh0tEFb3ZNVbEuMJKRD4M8RLquPGgZCqNUBIaDQsNRAEgDrEQbcYA8vIID9UUx2z5cK8UCUIZf2WqqTWvUwPrED1DiqerAh1kLyYZTaA7zekxkOfZlhuTGD3hppUWmkeDdvuZyoEsEsJbTUcgJgfgJA7AZUBM4TQBPKYM5NIeWGse+hhjEYSEBLIw7xl6cYbGSD2LOikjOd9ugiFv9F+PTzoYHS/hA64EkIxAaXbb8/D3kZt/biiAQQ6YekBOPkYW9wxBGxr9ohAY1AzYqQtLRnbWbZBB4Mp4mao4sn7VQO+c1sRZ1tqHLYWUJxJoCSO8sxN8L4vF7OSHBDQm5lNsmwATF6Nj0G2kDP6SXDoODYkgksT3imMrLdIc2G5ijoGb5Yi78AHJSIkVgODY+wJSfcEjeKFxDFbRxKlxNG6HsjDLg3A22gHOoVOo2yCK6IslupwZtSbEvDQTW9KwMFmTT4C4Cp5KJlY/NxFgGwZ87nTXW6Qc1uooyiGA+JQ+SSfsQNkYWdK/MbAdyAwQjFZY9qH/4FObAZMdBOWYiuJ4AnI5mO895lmVp6S+muy/rxcnkD0B9xwBuWNGy+9bp6LJbza0yKUiFNgfqI7NKgVONgi8RGM5SajWcTUgisRzp8EjJcs2o6cdjlDF2v9S+17cQLb7VlMcpYwyGGCmwfyVAagPSlBLfmNNMAANAS7ATlsoDwUzwPR54qKPlQ+BgBeLDF5hU453R5UpPq2vbt0RTVMTnJObSv6rEHZkOEIs8CGdaHcAB3REDoiQp7x+ZRYDk2OVMxwmXdXghKfcZLky828dx/d5R0hUgj3B8NAg3iz3EKOUOvHhIKGQmkRosyy8VsJQwNCQJ+CRkYHiiW9uO6LM+jkxNBIUgK67Hmh/SGrr5eXgqorrW4oFxs8HP7MD0DN3xkIQRa2i3iCB78CTqXUSLP0IysgmAbhPtRB8v6huB1JdUUUkgqLCXsqf/CAdXRIAP7FQZsRlQhFUccbFO3evxAZcupPgIYHLY9UvsURjlPHVvNQ93MIxmG3e0rArDAvifjLKXT9Y2YSiDFEqVDcGvkHEl2NuXq8dgn5kNjZXEEwcDj9DCOIDqjzHLBI9heR1CtEQWE+SxylPVEIE8i3ylMgiz0yUivwvdYFp2TJC0nGMEHOJIko90ZdFFbI1aAhChVtS+m7wZUa9L3jexXXOvA1VAI7vv6ikaFHtl+iKFvyjhs8xxQfk5KHxmWJwzDB0ypjENVnDyZ2OumSEJkr45uKqYItou86RCBUGRnzwbLLSzbmsu5xJGTDw1EK7EV9sQjnDKby7Y0vZ0OUJchJlIiu+77OWjD2fLH4q0wMkZDRnYyfXbvZM5zYISKPubUMi/Vlt0YCXZF9IKFxIRKQwCH8xieB5PnNUr4LgOOHIK1LIwa7fbX7D4A/LjvickXdsGmBvsrUtRLU9KNPOaVw41sWhp33caljM4UZsrcw4jouiEipiLNGDgYt251Odsq/Xm2CmWCvFXxbdxJADw2GaFESTSV5QWGY5uRjzBBacTEui51vUklAMjjkrdtGjWZj419OdySoxSgINdspSLi7E5d5sZlNPR9vJ8IVi+NzdJfEhV4+qAnsSzNczYx8NeFnrSW5Zf4HgJu6g7edYsDyZxWvTKBDa+W3SHhuK2IQEyg7/PbsFd1zHslI3OsFPbH0V3C5hGSZdqUrdM5ZSWR9Ghz1jxi5fTL7XC569Uky2M+tud7sIoAbqdNxz6dOqbWOcemx+XrBxzEHreE3IfKnEMulDfnm2TOlT3cY0gg+2fbVir8V6CT2HNTE4/rWeJIi0oSCUpgLd2v9mA2Mhp7/xnA48djT87weIO+GCQpwPtI1qENaVyerdv66Peegf7oAqAqnqa+H3ioQ3dk+OCkhhqQ1MQuylFFo83y+BUAh96AQAEC0t4IIfB8k7SNJ3fhmLBKz4H7+r0LcbRyoRBL9tSxyhdWM3Acth9GI6OM5ahIqbjffwMACO5pkCFrOamjF+KLJLmQJdFCe5DTtgx8PzueTD029Ybll+JUquaADORneLw1WnsWxmxD5MdvAVAGS7Q0G+vG0sLmhFkQFRTiO7fPVUsbTkKsSOLaIFoaGUTUo8yKkU6h1oYqRkfDKAdXSoft/nsAvLaJ8IUcSfdA5ABvOM7vkjr2fTkbgrdr6chcbwOqKrY5eUokZb4j80ZQlmoPuhUNQMD58WcAnH4EAxFLFN8TbxQJMBLkrF4e8M42AKaLbC3IWP5IxwOrJ6tiz6u3pcahyLT2+/0vEuD46TRKfchyhzN+TlpAIoF3AG8IpJfheZoI/jxwHpkopNPDgzYKRWi9HHjHnwdRq18mYPf04QUCq3dXAEQnyUBs4B3A0w0aB4UPMgfOA84e+jAjCiB6wAgAHQtN+Cc/L/PLJ61+HgFGUAJymTKRibcy6Cg2UM5YfwFwuWB5ECjTUwKO9TKc1wQel2j+Tk78ER3iL+v/CkDC0pYnOTjnnShIUiTGx+ZW/xbAjTMJXqaSOZ3CAo9BlCWGTAyR/6L+i78ZTK9+Pwf92HkSyFlzB1rKs0qePyR1fYrgC4A2ckrO8MwQwh+YwxFDAiswnkNJauQB/uP+TwCOUXAOLwdOfjEeOgnH4QXgixhGHqWy0YxMEgNpNEXgeAZiOczJuiduv/0IYfXXUfw7qM7HxGPTMm869T8hOEICMziNX451wWtHAy9ykgtYXqCyC3n//SdUqr9/GOGxfX4e/VixxWSvz/noJ4aq5VgrZ9McHZcZRI43mYRIkOAPebv/4TMB1d+n0leOx25QhgwzMBqF5jiNeB6BXq9C4+B9LnBgFb7Pg3RJyGa0/iOt+/3xp0/oVP9x/c8sg+SlKmIpDupdn2mZABSHu8Zyus3xYGl32QFk1mFxThv/7SMR1V+Wv8v6KweEt9fMtlzhOIABx+fgWeRBl0RucUJ/nPjntVCPv30go/rz8vtK/eNrfUcwyxHTUZOexxWTjMRPJWXgdfwhJ6N/+mDJPwM4nFC2j/3za3+TQZmrIwyZEhKmks5XsxRsn+uWt3/73G71x+XXtcj/UMK+HNvN52MuEwZP65Bgvdzl7/BHn9s/fRim+sPm2QgrEhg4Ng4ZrMfK7z/KTC0FDxTSwcFfnbPln//0cdXq5+XvW/kYB68prmnwn6wFS2d2lpGd+QAwlY/GeOFO/GQIj3A/369/gFD9nAKK43F8Yk1d+uwGdgU5Q8oD6pcKjv6gMCYGSiQLAsjvy/PDAOt/+ixM9Yvh5cPwchq65LvuFAAQnC2psjxi08SBE0ZhBD8PBs/1Pz6+COEz//2zywXAD5F9Qb0ey2HzfvbDZ548fweVsi5/2z2ZquNgWww9Bz6HkD4FACB8RbH9RRPV85M4J2AROFZMHP2EaoduWo/erChBrL1EZtbPnNCVvMeWxwngZwRFDD/+BOD+Wl70Lw+rfPwqdF0XnwPN6zpLY5afi/HSWjZMPeTRXW88XVZW/lkL/PXn+sdccL/vb1AP86UEeKAh6xeliHLE9CU7s4cjjAPprmPPa3oq4PM3APhRh99B+C9QLmVtv3r2PgAAAABJRU5ErkJggg==\" width=\"128\" height=\"128\" preserveAspectRatio=\"xMidYMid meet\"/>\n</svg>\n",
	"AGENTS.template.md": "# __PACKAGE_NAME__ — agent notes\n\nScaffolded from `@kentcdodds/package-app-kit/starter-tanstack`. Human Intent lives in README.\n\n- **Handoff:** open private hosted apps with **Open Package App** from kody.codes; re-open from the package page when you need a fresh session or service worker.\n\n## After scaffolding\n\nAfter `create-package-app-tanstack` lands this tree (or you copy `starter-tanstack/`), work through this checklist before treating the app as product-ready:\n\n1. **List mark (TanStack default included)** — This scaffold ships the square TanStack koala mark at `.kody/icon.png` / `.kody/icon.svg` (SVG wraps the PNG for text-only `packageSave`). Keep it unless the product needs its own brand — then replace with a distinctive square mark (do not reuse the kit demo race-driver). Prefer PNG on the Artifacts git lane. This is package / repo / community list identity.\n2. **PWA + in-app chrome marks (TanStack default included)** — Favicon / header / PWA icons already default to the same TanStack koala via `src/icon-png-bytes.ts` and `/icons/icon-192.png` + `/icons/icon-512.png` routes (git lane also has matching `icons/*.png`). Replace those bytes/files only when product-branding. Serve from **app routes** and point favicon / header `<img>` at `routes.…href()`. For a hero or alternate bust, add the same kind of route (for example `/brand/logo-full.png`). Keep list identity in `.kody/`; keep `public/` for CSS, `sw.js`, and other asset-host files.\n3. **Fill product identity** — Set `__APP_TITLE__` / title, description, and `packageName` (and related placeholders) to the real product names.\n4. **Keep local version writers** — Use this package's `src/version.ts` + `src/record-version.ts` against **this** app's `packageStorage`. Call **this** package's `./record-version` (`kody:__PACKAGE_NAME__/record-version`), not the kit's export.\n5. **Record each publish** — After every successful publish, call this package's `./record-version` with the published SHA (and message / `committedAt` when available).\n6. **TanStack recipe ([#2312](https://github.com/kody-hq/kody/pull/2312))** — Confirm root `tsconfig.json` has `\"jsx\": \"react-jsx\"` + `\"jsxImportSource\": \"react\"`; `app/router.ts` remounts the Request and `export default { fetch }`; `app/routeTree.tsx` builds `@tanstack/react-router` routes; SSR uses `react-dom/server.edge` `renderToReadableStream` in `app/ssr.tsx` (**no** TanStack Start / Vite); client `app/assets/entry.ts` calls `hydrateRoot` + `RouterProvider`.\n7. **Match manifest colors to the mark** — Set `themeColor` / `backgroundColor` in `buildWebManifest` (see `app/controllers/manifest.ts`) and the document `theme-color` meta so they harmonize with the mark.\n8. **Optional UI icons** — Add Lucide glyphs via `node scripts/add-lucide-icon.mjs <names>` into `app/icons/` when the UI needs icon-only or labeled controls.\n\nThen smoke with `packageAppFetch` (paths in **Smoke** below).\n\n## Kody CLI workflow\n\nThis package ships npm scripts around the Kody CLI (`@kodycodes/cli`, pinned in `devDependencies`, so run `npm install` once). Each script is a thin wrapper; the raw command is listed so agents without npm (or with only MCP) can follow along.\n\n| Step | Script | Raw command |\n| --- | --- | --- |\n| Sign in + git access (humans) | `npm run setup` | `kody login && kody auth setup-git` |\n| Pull | — | `git clone https://kody.codes/__PACKAGE_NAME__.git` then `git pull` |\n| Run locally (mock) | `npm run dev` | `kody dev --target mock` |\n| Run against real data | `npm run dev:prod` | `kody dev --target prod` |\n| Fetch one path | `npm run fetch -- /health` | `kody dev fetch /health` |\n| Run a job once | `npm run job -- <name>` | `kody dev job <name>` |\n| Send a webhook | `npm run webhook -- <name> --body fixtures/webhook.example.json` | `kody dev webhook <name> --body <file>` |\n| Emit an event | `npm run emit -- <event> --body fixtures/event.example.json` | `kody dev emit <event> --body <file>` |\n| Bundle + publish checks | `npm run check` | `kody dev check` |\n| Types | `npm run typecheck` | `tsc --noEmit` |\n| Tests | `npm test` | `node --test` (smoke tests call `kody dev fetch` on mock) |\n| Push | — | `git push` |\n| Publish | `npm run publish:kody` (add `-- <package_id>` if your CLI rejects the name) | tests, `node scripts/prepublish-check.mjs`, then `kody package publish <package_id or __PACKAGE_NAME__>` |\n\nLoop: pull, edit, `npm run dev` (or `npm run fetch -- <path>`), `npm test`, commit, `git push`, `npm run publish:kody`. Add `--dry-run` to `job` / `webhook` / `emit` to see what would run without side effects. Mock is the default target: scratch storage and fake credentials, so unpublished code never touches real data. Use `dev:prod` only when you mean to.\n\n### Signing in (explicit token lifetimes)\n\n- **Humans:** `npm run setup` (browser `kody login`, then `kody auth setup-git`, which defaults to `--lifetime long`).\n- **Agents on a task:** get a one-shot `kody_bc_…` code from MCP `cliCredentialBootstrap`, then `npx kody auth bootstrap --code <code> --lifetime short` (1h idle / 24h max). Only use `--lifetime long` (14d idle / 3mo max) for a long-running agent the owner set up on purpose. Then `npx kody auth setup-git --lifetime short` for git access.\n- Always pass `--lifetime` explicitly. Never print, log, commit, or paste tokens. Do not call `kody api cliCredentialBootstrapRedeem` (it prints the token). `kody status` / `kody whoami` confirm sign-in without showing it.\n- Package code reads Kody **credentials** through the runtime, never from files in this repo. `scripts/prepublish-check.mjs` refuses to publish if a tracked file looks like it contains one.\n\n### Without a shell (MCP-only agents)\n\nScripts are optional. The same loop works through Kody MCP tools: edit files with `packageSave` (or repo sessions), publish with `packagePublishExternalPush`, and smoke with `packageAppFetch` (see **Smoke** below). Before publishing, re-read **After scaffolding** and confirm README/AGENTS still describe the app.\n\n\n## TanStack recipe (framework-agnostic host)\n\n```json\n\"kody\": {\n  \"app\": {\n    \"entry\": \"./app/router.ts\",\n    \"client\": \"./app/assets/entry.ts\",\n    \"assets\": \"./public\"\n  }\n}\n```\n\n- **tsconfig** — root `tsconfig.json` with `\"jsx\": \"react-jsx\"` and `\"jsxImportSource\": \"react\"`.\n- **Router** `app/router.ts` — remount the Request for `appBasePath`, then `export default { fetch(request) { … } }` dispatching API routes + `renderApp` SSR.\n- **Route tree** `app/routeTree.tsx` — `@tanstack/react-router` `createRootRouteWithContext` + page routes; `basepath` matches the mount.\n- **SSR** `app/ssr.tsx` — `createMemoryHistory` + `renderToReadableStream(<RouterProvider />)` (Workers-safe; **not** TanStack Start).\n- **Routes helpers** `app/routes.ts` — `createRoutes(appBasePath)` for hrefs (no `kody:runtime` in this module).\n- **Client** `app/assets/entry.ts` — `hydrateRoot(document, <RouterProvider />)` + SW register.\n- **Assets** `public/` — `styles.css`, `sw.js` (reads `__version.json`; no content hash in source).\n\n## Imports — React + TanStack Router (no Vite / Start)\n\n```ts\nimport {\n  createRouter,\n  createRootRouteWithContext,\n  createRoute,\n  createMemoryHistory,\n  createBrowserHistory,\n  RouterProvider,\n  Link,\n} from '@tanstack/react-router'\nimport { renderToReadableStream } from 'react-dom/server.edge'\nimport { hydrateRoot } from 'react-dom/client'\nimport { packageContext, packageStorage } from 'kody:runtime'\n```\n\n**Forbid:**\n\n- TanStack Start / Vite / Pitlane tooling inside the Worker publish\n- `remix/component` JSX pragmas (this starter is standard React)\n- Importing `kody:` / `kody:@…` from the browser client graph\n\n## Kit (Worker / SSR only)\n\n```ts\nimport { formatAboutPageData } from 'kody:@kentcdodds/package-app-kit/about'\nimport { buildWebManifest } from 'kody:@kentcdodds/package-app-kit/sw'\nimport { starterManifestIcons, iconPngResponse } from '../src/icon-png-bytes.ts'\n```\n\n**Version metadata:** use this package's local `./record-version` (`src/record-version.ts` → this app's `packageStorage`). Do not re-export or call `kody:@kentcdodds/package-app-kit/record-version` from execute — that writes the kit's storage and fails provenance. About / `/api/version` import `readRecordedVersion` relatively from `../../src/record-version.ts`.\n\nBrowser islands must **not** import `kody:` / `kody:@…`.\n\n## List / identity mark\n\nPut the package list mark at `.kody/icon.png` (also `.svg` / `.webp` / `.jpg` / `.jpeg`). This scaffold includes the **TanStack koala** mark (`.kody/icon.png` + `.kody/icon.svg` base64 wrapper) — keep it unless replacing with the product's official logo (prefer PNG on the git lane). Do **not** add root `community-icon.*`. Do **not** move PWA icons (`icons/icon-192.png`, routes served by `src/icon-png-bytes.ts`) into `.kody/`.\n\n## PWA / SW\n\nRegister `${assetBasePath}/sw.js` with `{ scope: `${appBasePath}/` }`. Precache via `GET …/_assets/__version.json` → `clientModuleUrl`. Icons via starter `src/icon-png-bytes.ts` routes (192 + 512; TanStack koala default). Manifest `crossorigin=\"use-credentials\"`.\n\n\n## Optional L3 — Realtime\n\nPackage apps can add live sync with kit `./realtime` + `./realtime-client` (see kit AGENTS **L3 — Realtime**). Typical recipe: export `handleRealtimeEvent` from `app/router.ts`, broadcast after durable writes via `KodyRuntime.realtime`, connect from a browser island with `connectPackageRealtime`. Do not wire WebSocket into every scaffold by default — adopt when the product needs multi-session notify (Notes-style).\n\n## Smoke\n\n```ts\nimport { kody } from 'kody:runtime'\nexport default async function main() {\n  const paths = ['/', '/notes', '/about', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/icon-512.png', '/health']\n  const out = []\n  for (const path of paths) {\n    const res = await kody.packageAppFetch({ kody_id: '__PACKAGE_ID__', path })\n    out.push({ path, status: res.status, type: res.headers?.['content-type'] || res.headers?.['Content-Type'] })\n  }\n  return out\n}\n```\n\n## Prefer zero content layout shift (CLS)\n\nKeep document flow stable. Update-available, toasts, and other post-hydration chrome use **fixed overlays** (see `UpdateBanner` / `.pak-update-toast`). Do not inject in-flow banners that push content down. Reserve height for async UI; hide install chrome in `display-mode: standalone`.\n\n\n## Tooltips (`data-tip`)\n\nTip when the tip adds information: icon-only install in the header, truncated SHA, ISO behind local time. Labeled nav and action buttons rely on their visible words. Color swatches use visible text labels.\n\n## Install header icon\n\nPlace `InstallCta` in the header brand row **to the right of the title**. Keep a reserved same-size slot (`pak-install-slot`) so BIP reveal does not shift layout. No large in-flow Install button.\n\n## Optimistic UI + press feedback\n\nImmediate `data-pending` / `aria-busy` on press. Use `app/ui/busy.ts` (`createBusyGate`) for spin-delay timing: delay busy labels ~200ms, keep them ≥400ms once shown, so fast ops do not flash \"Loading…\".\n\n\n## Lucide icons\n\n```bash\n# From the kit repo while editing the starter twin, or copy script into the app:\nnode scripts/add-lucide-icon.mjs download trash-2 plus\n```\n\nImport generated React SVG components from `app/icons/{name}.tsx`. Icon-only controls get `data-tip` + `aria-label`; labeled buttons keep visible text.\n\n## packageStorage migrations\n\n```ts\nimport { packageStorage } from 'kody:runtime'\nimport {\n  createMigrationRunner,\n} from 'kody:@kentcdodds/package-app-kit/storage-migrations'\n\nconst ensureSchema = createMigrationRunner({\n  storage: packageStorage(),\n  versionKey: '__PACKAGE_ID__-schema-version',\n  migrations: [/* { version, name, up } */],\n})\nawait ensureSchema()\n```\n\nSee `app/data/notes.ts` for a copy-paste example.\n",
	"README.template.md": "# __APP_TITLE__\n\n## Intent\n\nDesktop + mobile friendly Kody package app scaffolded from `@kentcdodds/package-app-kit` `starter-tanstack/`.\nReplace this Intent with the product goal before the first real publish.\n\n## Stack\n\n- **TanStack Router + React** under the framework-agnostic host — `app/router.ts` remounts + `export default { fetch }` (no `kody.app.runtime`)\n- Root `tsconfig.json` with `\"jsx\": \"react-jsx\"` + `\"jsxImportSource\": \"react\"` (standard React — not remix/component)\n- `@tanstack/react-router` route tree (`app/routeTree.tsx`) with Workers SSR via `react-dom/server.edge` `renderToReadableStream` (**no** TanStack Start / Vite)\n- Mount-prefixed routes (`basepath` + remount) and `createRoutes(appBasePath)` href helpers\n- Controllers + notes POST with `packageStorage()`; JSON `Accept` for client loaders\n- Browser `hydrateRoot` entry (`app/assets/entry.ts`) + `RouterProvider`\n- Static assets (`public/styles.css`, `public/sw.js` via `__version.json`)\n- Kit: design tokens CSS, record-version / About, install CTA + update banner, Notes form + double-check\n\n## List mark\n\nIdentity/list icon: `.kody/icon.png` (scaffold ships `.kody/icon.svg`). Replace with your logo. PWA icons stay under `/icons/` via the kit — not in `.kody/`.\n\n## After scaffolding\n\nSee **After scaffolding** in [`AGENTS.md`](./AGENTS.md): brand the list mark + PWA icons, fill titles/description, keep local `record-version`, confirm the TanStack recipe, and match manifest theme colors to the mark.\n\n## Develop locally\n\nYou need Node 24+ and git.\n\n```bash\ngit clone https://kody.codes/__PACKAGE_NAME__.git\ncd __PACKAGE_ID__\nnpm install\nnpm run setup   # first time only: kody login + git access\nnpm run dev     # local app with mock data and fake credentials\n```\n\nCommon scripts:\n\n- `npm run dev`: run the app locally on mock data (`npm run dev:prod` uses your real data)\n- `npm run fetch -- /health`: request one path without opening a browser\n- `npm run job -- <name>`, `npm run webhook -- <name> --body fixtures/webhook.example.json`, `npm run emit -- <event> --body fixtures/event.example.json`: trigger jobs, webhooks, and events once\n- `npm run check`, `npm run typecheck`, `npm test`: catch problems before you ship\n- `npm run publish:kody`: run tests and the pre-publish check, then publish what you pushed\n\nShip changes with `git push`, then `npm run publish:kody`. No terminal? Ask your Kody agent to make and publish changes instead; [`AGENTS.md`](./AGENTS.md) has the full workflow.\n\n\n## After publish\n\nCall **this package's** `./record-version` (local `packageStorage`), not the kit's:\n\n```ts\nimport recordVersion from 'kody:__PACKAGE_NAME__/record-version'\nawait recordVersion({ sha: '<published_commit>', message: '<commit message>' })\n```\n",
	"app/assets/entry.ts": "import { hydrateRoot } from 'react-dom/client'\nimport { createElement } from 'react'\nimport { createBrowserHistory, RouterProvider } from '@tanstack/react-router'\nimport { createAppRouter, type RouterContext } from '../routeTree.tsx'\n\nfunction readContext(): RouterContext {\n\tconst root = document.documentElement\n\treturn {\n\t\tappBasePath: root.getAttribute('data-app-base') || '',\n\t\tassetBasePath: root.getAttribute('data-asset-base') || '',\n\t\tclientModuleUrl: root.getAttribute('data-client-module'),\n\t\trunningSha: root.getAttribute('data-running-sha') || '',\n\t}\n}\n\nfunction registerServiceWorker() {\n\tif (!('serviceWorker' in navigator)) return\n\tconst root = document.documentElement\n\tconst appBase = (root.getAttribute('data-app-base') || '').replace(/\\/$/, '')\n\tconst assetBase = (root.getAttribute('data-asset-base') || '').replace(/\\/$/, '')\n\tconst swUrl =\n\t\troot.getAttribute('data-sw-url') ||\n\t\t(assetBase ? `${assetBase}/sw.js` : `${appBase}/sw.js`)\n\tif (!appBase || !swUrl) return\n\tvoid navigator.serviceWorker\n\t\t.register(swUrl, { scope: `${appBase}/`, updateViaCache: 'none' })\n\t\t.catch((error) => console.warn('SW register failed', error))\n}\n\nconst context = readContext()\nconst basepath = context.appBasePath.replace(/\\/+$/, '') || undefined\nconst history = createBrowserHistory()\nconst router = createAppRouter({ history, context, basepath })\n\nvoid router.load().then(() => {\n\thydrateRoot(document, createElement(RouterProvider, { router }))\n\tdocument.documentElement.dataset.hydrated = 'true'\n\tregisterServiceWorker()\n})\n",
	"app/controllers/icons.ts": "import { iconPngResponse } from '../../src/icon-png-bytes.ts'\n\nexport async function icon192() {\n\treturn iconPngResponse(192)\n}\n\nexport async function icon512() {\n\treturn iconPngResponse(512)\n}\n",
	"app/controllers/manifest.ts": "import { packageContext } from 'kody:runtime'\nimport { buildWebManifest } from 'kody:@kentcdodds/package-app-kit/sw'\nimport { starterManifestIcons } from '../../src/icon-png-bytes.ts'\n\nexport async function manifest() {\n\tconst appBasePath = packageContext?.appBasePath ?? ''\n\tconst body = buildWebManifest({\n\t\tappBasePath,\n\t\tname: '__APP_TITLE__',\n\t\tshortName: '__APP_TITLE__',\n\t\tdescription: 'Scaffolded from @kentcdodds/package-app-kit starter-tanstack/.',\n\t\tthemeColor: '#0a0a0a',\n\t\tbackgroundColor: '#0a0a0a',\n\t\ticons: starterManifestIcons(appBasePath),\n\t})\n\treturn new Response(JSON.stringify(body), {\n\t\theaders: {\n\t\t\t'content-type': 'application/manifest+json; charset=utf-8',\n\t\t\t'cache-control': 'no-store',\n\t\t},\n\t})\n}\n",
	"app/controllers/notes-action.ts": "import { addNote, deleteNote } from '../data/notes.ts'\nimport { createRoutes } from '../routes.ts'\n\nfunction wantsJson(request: Request) {\n\tconst accept = (request.headers.get('accept') || '').toLowerCase()\n\treturn accept.startsWith('application/json')\n}\n\n/**\n * Notes form POST (add / delete). Mirrors Remix starter progressive-enhancement contract.\n */\nfunction redirectTo(request: Request, location: string, status = 303) {\n\tconst absolute = new URL(location, request.url).toString()\n\treturn Response.redirect(absolute, status)\n}\n\nexport async function notesAction(request: Request, appBasePath: string) {\n\tconst form = await request.formData()\n\tconst intent = String(form.get('intent') ?? '')\n\tconst json = wantsJson(request)\n\tconst routes = createRoutes(appBasePath)\n\n\tif (intent === 'delete') {\n\t\tconst id = String(form.get('id') || '').trim()\n\t\tconst result = id ? await deleteNote(id) : { ok: false }\n\t\tif (json) return Response.json({ ok: Boolean(result?.ok), id })\n\t\treturn redirectTo(request, routes.notes.index.href())\n\t}\n\n\tconst text = String(form.get('text') || '').trim()\n\tif (!text) {\n\t\tif (json) {\n\t\t\treturn Response.json({ ok: false, error: 'A note needs some text.' }, { status: 400 })\n\t\t}\n\t\treturn redirectTo(request, routes.notes.index.href())\n\t}\n\n\tconst note = await addNote(text)\n\tif (json) return Response.json({ ok: true, note })\n\treturn redirectTo(request, routes.notes.index.href())\n}\n",
	"app/controllers/version.ts": "import { readRecordedVersion } from '../../src/record-version.ts'\n\nexport async function version() {\n\tconst recorded = await readRecordedVersion()\n\treturn Response.json(recorded, {\n\t\theaders: { 'cache-control': 'no-store' },\n\t})\n}\n",
	"app/data/notes.ts": "import { packageStorage } from 'kody:runtime'\nimport { createMigrationRunner } from 'kody:@kentcdodds/package-app-kit/storage-migrations'\n\nexport type Note = {\n\tid: string\n\ttext: string\n\tcreatedAt: string\n}\n\n/** Document shape after schema v1. */\ntype NotesDocument = {\n\titems: Array<Note>\n}\n\nconst notesKey = '__PACKAGE_ID__-notes'\nconst legacyNotesKey = '__PACKAGE_ID__-notes-v1'\nconst schemaVersionKey = '__PACKAGE_ID__-schema-version'\n\nfunction getStorage() {\n\treturn packageStorage()\n}\n\n/**\n * Demo schema bump: legacy flat array key → `{ items: Note[] }` document.\n * Runner is isolate-memoized; safe to await on every notes read/write.\n */\nconst ensureNotesSchema = (() => {\n\tlet ensure: ReturnType<typeof createMigrationRunner> | null = null\n\treturn () => {\n\t\tif (!ensure) {\n\t\t\tconst storage = getStorage()\n\t\t\tensure = createMigrationRunner({\n\t\t\t\tstorage,\n\t\t\t\tversionKey: schemaVersionKey,\n\t\t\t\tmigrations: [\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\tname: 'notes-array-to-document',\n\t\t\t\t\t\tasync up(s: any) {\n\t\t\t\t\t\t\tconst legacy = await s.get(legacyNotesKey)\n\t\t\t\t\t\t\tconst existing = await s.get(notesKey)\n\t\t\t\t\t\t\tif (Array.isArray(legacy)) {\n\t\t\t\t\t\t\t\tawait s.set(notesKey, { items: legacy } satisfies NotesDocument)\n\t\t\t\t\t\t\t\tawait s.delete?.(legacyNotesKey)\n\t\t\t\t\t\t\t} else if (Array.isArray(existing)) {\n\t\t\t\t\t\t\t\tawait s.set(notesKey, { items: existing } satisfies NotesDocument)\n\t\t\t\t\t\t\t} else if (existing == null) {\n\t\t\t\t\t\t\t\tawait s.set(notesKey, { items: [] } satisfies NotesDocument)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t}\n\t\treturn ensure()\n\t}\n})()\n\nasync function readDocument(): Promise<NotesDocument> {\n\tawait ensureNotesSchema()\n\tconst stored = await getStorage().get(notesKey)\n\tif (stored && typeof stored === 'object' && Array.isArray((stored as NotesDocument).items)) {\n\t\treturn stored as NotesDocument\n\t}\n\treturn { items: [] }\n}\n\nexport async function listNotes(): Promise<Array<Note>> {\n\tconst doc = await readDocument()\n\treturn doc.items\n}\n\nexport async function addNote(text: string) {\n\tconst storage = getStorage()\n\tconst doc = await readDocument()\n\tconst note: Note = {\n\t\tid: crypto.randomUUID(),\n\t\ttext,\n\t\tcreatedAt: new Date().toISOString(),\n\t}\n\tconst next: NotesDocument = { items: [note, ...doc.items] }\n\tawait storage.set(notesKey, next)\n\treturn note\n}\n\nexport async function deleteNote(id: string) {\n\tconst storage = getStorage()\n\tconst doc = await readDocument()\n\tconst items = doc.items.filter((note) => note.id !== id)\n\tawait storage.set(notesKey, { items } satisfies NotesDocument)\n\treturn { ok: items.length !== doc.items.length }\n}\n",
	"app/icons/ATTRIBUTION.md": "# Icon attribution\n\nIcons in this folder are from [Lucide](https://lucide.dev) ([GitHub](https://github.com/lucide-icons/lucide)).\n\n- License: **ISC** (and MIT for icons derived from Feather — see Lucide LICENSE)\n- Added via `node scripts/add-lucide-icon.mjs <name…>` from `lucide-static@0.544.0`\n- Prefer checked-in SVG + generated TSX over bundling the full Lucide package\n\nKeep this notice when copying icons into scaffolded apps.\n",
	"app/icons/download.svg": "<svg\n  class=\"lucide lucide-download\"\n  xmlns=\"http://www.w3.org/2000/svg\"\n  width=\"24\"\n  height=\"24\"\n  viewBox=\"0 0 24 24\"\n  fill=\"none\"\n  stroke=\"currentColor\"\n  stroke-width=\"2\"\n  stroke-linecap=\"round\"\n  stroke-linejoin=\"round\"\n>\n  <path d=\"M12 15V3\" />\n  <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n  <path d=\"m7 10 5 5 5-5\" />\n</svg>\n",
	"app/icons/download.tsx": "import type { SVGProps } from 'react'\n\nexport type IconDownloadProps = SVGProps<SVGSVGElement> & {\n\tsize?: number\n}\n\n/** Lucide icon `download` (ISC) — React SVG component. */\nexport function IconDownload({ size = 20, className, ...rest }: IconDownloadProps) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName={className}\n\t\t\taria-hidden=\"true\"\n\t\t\tdata-lucide=\"download\"\n\t\t\t{...rest}\n\t\t>\n\t\t\t<path d=\"M12 15V3\" />\n\t\t\t<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n\t\t\t<path d=\"m7 10 5 5 5-5\" />\n\t\t</svg>\n\t)\n}\n",
	"app/icons/plus.svg": "<svg\n  class=\"lucide lucide-plus\"\n  xmlns=\"http://www.w3.org/2000/svg\"\n  width=\"24\"\n  height=\"24\"\n  viewBox=\"0 0 24 24\"\n  fill=\"none\"\n  stroke=\"currentColor\"\n  stroke-width=\"2\"\n  stroke-linecap=\"round\"\n  stroke-linejoin=\"round\"\n>\n  <path d=\"M5 12h14\" />\n  <path d=\"M12 5v14\" />\n</svg>\n",
	"app/icons/plus.tsx": "import type { SVGProps } from 'react'\n\nexport type IconPlusProps = SVGProps<SVGSVGElement> & {\n\tsize?: number\n}\n\n/** Lucide icon `plus` (ISC) — React SVG component. */\nexport function IconPlus({ size = 20, className, ...rest }: IconPlusProps) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName={className}\n\t\t\taria-hidden=\"true\"\n\t\t\tdata-lucide=\"plus\"\n\t\t\t{...rest}\n\t\t>\n\t\t\t<path d=\"M5 12h14\" />\n\t\t\t<path d=\"M12 5v14\" />\n\t\t</svg>\n\t)\n}\n",
	"app/icons/trash-2.svg": "<svg\n  class=\"lucide lucide-trash-2\"\n  xmlns=\"http://www.w3.org/2000/svg\"\n  width=\"24\"\n  height=\"24\"\n  viewBox=\"0 0 24 24\"\n  fill=\"none\"\n  stroke=\"currentColor\"\n  stroke-width=\"2\"\n  stroke-linecap=\"round\"\n  stroke-linejoin=\"round\"\n>\n  <path d=\"M10 11v6\" />\n  <path d=\"M14 11v6\" />\n  <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\" />\n  <path d=\"M3 6h18\" />\n  <path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n</svg>\n",
	"app/icons/trash-2.tsx": "import type { SVGProps } from 'react'\n\nexport type IconTrash2Props = SVGProps<SVGSVGElement> & {\n\tsize?: number\n}\n\n/** Lucide icon `trash-2` (ISC) — React SVG component. */\nexport function IconTrash2({ size = 20, className, ...rest }: IconTrash2Props) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName={className}\n\t\t\taria-hidden=\"true\"\n\t\t\tdata-lucide=\"trash-2\"\n\t\t\t{...rest}\n\t\t>\n\t\t\t<path d=\"M10 11v6\" />\n\t\t\t<path d=\"M14 11v6\" />\n\t\t\t<path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\" />\n\t\t\t<path d=\"M3 6h18\" />\n\t\t\t<path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n\t\t</svg>\n\t)\n}\n",
	"app/middleware/request-id.ts": "export const REQUEST_ID_HEADER = 'x-request-id'\n\nexport function createRequestId(): string {\n\treturn crypto.randomUUID()\n}\n\nexport function withRequestId(response: Response, requestId: string): Response {\n\tconst headers = new Headers(response.headers)\n\theaders.set(REQUEST_ID_HEADER, requestId)\n\treturn new Response(response.body, {\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t\theaders,\n\t})\n}\n",
	"app/pages/about.tsx": "import { getRouteApi } from '@tanstack/react-router'\nimport { AppShell } from '../ui/layout.tsx'\nimport { AboutPanel } from '../ui/about-panel.tsx'\n\nconst routeApi = getRouteApi('/about')\n\nexport function AboutPage() {\n\tconst about = routeApi.useLoaderData()\n\treturn (\n\t\t<AppShell page=\"about\" heading=\"About\">\n\t\t\t<AboutPanel\n\t\t\t\tsha={about.sha}\n\t\t\t\tshortSha={about.shortSha}\n\t\t\t\tmessage={about.message}\n\t\t\t\tpublishedAt={about.publishedAt}\n\t\t\t\tcommittedAt={about.committedAt}\n\t\t\t\tcodeUrl={about.codeUrl}\n\t\t\t\trunningSha={about.runningSha || about.sha}\n\t\t\t\tappId=\"__PACKAGE_ID__\"\n\t\t\t/>\n\t\t</AppShell>\n\t)\n}\n",
	"app/pages/home.tsx": "import { Link, getRouteApi } from '@tanstack/react-router'\nimport { AppShell } from '../ui/layout.tsx'\nimport { Counter } from '../ui/counter.tsx'\n\nconst routeApi = getRouteApi('/')\n\nexport function HomePage() {\n\tconst { noteCount } = routeApi.useLoaderData()\n\treturn (\n\t\t<AppShell page=\"home\" heading=\"__APP_TITLE__\">\n\t\t\t<section className=\"pak-card pak-stack\" aria-label=\"Design tokens\">\n\t\t\t\t<p className=\"pak-eyebrow\">Colors</p>\n\t\t\t\t<div className=\"pak-swatches\">\n\t\t\t\t\t<span className=\"pak-swatch-label\">\n\t\t\t\t\t\t<span className=\"pak-swatch\" style={{ background: 'var(--accent)' }} aria-hidden=\"true\" />\n\t\t\t\t\t\tAccent\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"pak-swatch-label\">\n\t\t\t\t\t\t<span className=\"pak-swatch\" style={{ background: 'var(--ink)' }} aria-hidden=\"true\" />\n\t\t\t\t\t\tInk\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"pak-swatch-label\">\n\t\t\t\t\t\t<span className=\"pak-swatch\" style={{ background: 'var(--surface)' }} aria-hidden=\"true\" />\n\t\t\t\t\t\tSurface\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"pak-swatch-label\">\n\t\t\t\t\t\t<span className=\"pak-swatch\" style={{ background: 'var(--good)' }} aria-hidden=\"true\" />\n\t\t\t\t\t\tGood\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<section className=\"pak-card pak-stack\" aria-label=\"Notes\">\n\t\t\t\t<p className=\"pak-eyebrow\">Notes</p>\n\t\t\t\t<Link\n\t\t\t\t\tto=\"/notes\"\n\t\t\t\t\tclassName=\"pak-btn pak-btn-accent\"\n\t\t\t\t\taria-label={`Notes (${noteCount})`}\n\t\t\t\t>\n\t\t\t\t\tNotes\n\t\t\t\t\t<span className=\"pak-pill\" aria-hidden=\"true\">\n\t\t\t\t\t\t{noteCount}\n\t\t\t\t\t</span>\n\t\t\t\t</Link>\n\t\t\t</section>\n\n\t\t\t<section className=\"pak-card pak-stack\" aria-label=\"TanStack React pattern\">\n\t\t\t\t<p className=\"pak-eyebrow\">Pattern example</p>\n\t\t\t\t<p className=\"pak-muted\">\n\t\t\t\t\tHydrated React state — copy this shape for interactive bits.\n\t\t\t\t</p>\n\t\t\t\t<div className=\"pak-cluster\">\n\t\t\t\t\t<Counter initialCount={0} label=\"Taps\" />\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</AppShell>\n\t)\n}\n",
	"app/pages/notes.tsx": "import { getRouteApi } from '@tanstack/react-router'\nimport { AppShell } from '../ui/layout.tsx'\nimport { NotesDemo } from '../ui/notes-demo.tsx'\nimport { createRoutes } from '../routes.ts'\nimport { useAppShell } from '../router-context.ts'\n\nconst routeApi = getRouteApi('/notes')\n\nexport function NotesPage() {\n\tconst { notes } = routeApi.useLoaderData()\n\tconst { appBasePath } = useAppShell()\n\tconst routes = createRoutes(appBasePath)\n\treturn (\n\t\t<AppShell page=\"notes\" heading=\"Notes\">\n\t\t\t<NotesDemo notes={notes} actionHref={routes.notes.action.href()} />\n\t\t</AppShell>\n\t)\n}\n",
	"app/routeTree.tsx": "import {\n\tOutlet,\n\tcreateRootRouteWithContext,\n\tcreateRoute,\n\tcreateRouter,\n\tuseRouter,\n\tuseRouterState,\n} from '@tanstack/react-router'\nimport type { RouterHistory } from '@tanstack/react-router'\nimport type { AppShellContext } from './router-context.ts'\nimport { AppShellReactContext } from './router-context.ts'\nimport { Document } from './ui/document.tsx'\nimport { ErrorBanner } from './ui/error-banner.tsx'\nimport { HomePage } from './pages/home.tsx'\nimport { AboutPage } from './pages/about.tsx'\nimport { NotesPage } from './pages/notes.tsx'\nimport { toErrorMessage } from './ui/to-error-message.ts'\nimport type { Note } from './data/notes.ts'\n\nexport type RouterContext = AppShellContext\n\ntype PageStaticData = { page?: string; title?: string }\n\nconst rootRoute = createRootRouteWithContext<RouterContext>()({\n\tcomponent: function Root() {\n\t\tconst router = useRouter()\n\t\tconst ctx = router.options.context as RouterContext\n\t\tconst leaf = useRouterState({ select: (s) => s.matches.at(-1) })\n\t\tconst staticData = (leaf?.staticData || {}) as PageStaticData\n\t\treturn (\n\t\t\t<AppShellReactContext.Provider value={ctx}>\n\t\t\t\t<Document\n\t\t\t\t\ttitle={staticData.title || '__APP_TITLE__'}\n\t\t\t\t\tpage={staticData.page || ''}\n\t\t\t\t\trunningSha={ctx.runningSha}\n\t\t\t\t\tappBasePath={ctx.appBasePath}\n\t\t\t\t\tassetBasePath={ctx.assetBasePath}\n\t\t\t\t\tclientModuleUrl={ctx.clientModuleUrl}\n\t\t\t\t>\n\t\t\t\t\t<Outlet />\n\t\t\t\t</Document>\n\t\t\t</AppShellReactContext.Provider>\n\t\t)\n\t},\n\terrorComponent: function RootError({ error }) {\n\t\tconst router = useRouter()\n\t\tconst ctx = router.options.context as RouterContext\n\t\treturn (\n\t\t\t<AppShellReactContext.Provider value={ctx}>\n\t\t\t\t<Document\n\t\t\t\t\ttitle=\"Error · __APP_TITLE__\"\n\t\t\t\t\tappBasePath={ctx.appBasePath}\n\t\t\t\t\tassetBasePath={ctx.assetBasePath}\n\t\t\t\t\tclientModuleUrl={ctx.clientModuleUrl}\n\t\t\t\t\trunningSha={ctx.runningSha}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"pak-wrap pak-stack\">\n\t\t\t\t\t\t<ErrorBanner\n\t\t\t\t\t\t\ttitle=\"Something went wrong\"\n\t\t\t\t\t\t\tmessage={toErrorMessage(error)}\n\t\t\t\t\t\t\tdetail={toErrorMessage(error)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</Document>\n\t\t\t</AppShellReactContext.Provider>\n\t\t)\n\t},\n})\n\nconst homeRoute = createRoute({\n\tgetParentRoute: () => rootRoute,\n\tpath: '/',\n\tstaticData: { page: 'home', title: '__APP_TITLE__' } satisfies PageStaticData,\n\tloader: async ({ context }): Promise<{ noteCount: number }> => {\n\t\tif (context.listNotes) {\n\t\t\tconst notes = await context.listNotes()\n\t\t\treturn { noteCount: notes.length }\n\t\t}\n\t\tconst appBase = context.appBasePath.replace(/\\/$/, '')\n\t\tconst res = await fetch(`${appBase}/notes`, {\n\t\t\theaders: { Accept: 'application/json' },\n\t\t\tcredentials: 'same-origin',\n\t\t})\n\t\tif (!res.ok) return { noteCount: 0 }\n\t\tconst data = (await res.json()) as { notes?: Array<Note> }\n\t\treturn { noteCount: Array.isArray(data.notes) ? data.notes.length : 0 }\n\t},\n\tcomponent: HomePage,\n})\n\nconst aboutRoute = createRoute({\n\tgetParentRoute: () => rootRoute,\n\tpath: '/about',\n\tstaticData: { page: 'about', title: 'About · __APP_TITLE__' } satisfies PageStaticData,\n\tloader: async ({ context }) => {\n\t\tif (context.loadAbout) return await context.loadAbout()\n\t\tconst appBase = context.appBasePath.replace(/\\/$/, '')\n\t\tconst res = await fetch(`${appBase}/api/version`, {\n\t\t\tcache: 'no-store',\n\t\t\tcredentials: 'same-origin',\n\t\t})\n\t\tconst version = res.ok\n\t\t\t? ((await res.json()) as {\n\t\t\t\t\tsha?: string\n\t\t\t\t\tshortSha?: string\n\t\t\t\t\tmessage?: string\n\t\t\t\t\tpublishedAt?: string\n\t\t\t\t\tcommittedAt?: string\n\t\t\t\t})\n\t\t\t: {}\n\t\treturn {\n\t\t\tsha: version.sha || '',\n\t\t\tshortSha: version.shortSha || '',\n\t\t\tmessage: version.message || '',\n\t\t\tpublishedAt: version.publishedAt || '',\n\t\t\tcommittedAt: version.committedAt || '',\n\t\t\tcodeUrl: null as string | null,\n\t\t\trunningSha: version.sha || '',\n\t\t}\n\t},\n\tcomponent: AboutPage,\n})\n\nconst notesRoute = createRoute({\n\tgetParentRoute: () => rootRoute,\n\tpath: '/notes',\n\tstaticData: { page: 'notes', title: 'Notes · __APP_TITLE__' } satisfies PageStaticData,\n\tloader: async ({ context }): Promise<{ notes: Array<Note> }> => {\n\t\tif (context.listNotes) {\n\t\t\treturn { notes: await context.listNotes() }\n\t\t}\n\t\tconst appBase = context.appBasePath.replace(/\\/$/, '')\n\t\tconst res = await fetch(`${appBase}/notes`, {\n\t\t\theaders: { Accept: 'application/json' },\n\t\t\tcredentials: 'same-origin',\n\t\t})\n\t\tif (!res.ok) return { notes: [] }\n\t\tconst data = (await res.json()) as { notes?: Array<Note> }\n\t\treturn { notes: Array.isArray(data.notes) ? data.notes : [] }\n\t},\n\tcomponent: NotesPage,\n})\n\nexport const routeTree = rootRoute.addChildren([homeRoute, aboutRoute, notesRoute])\n\nexport function createAppRouter(input: {\n\thistory: RouterHistory\n\tcontext: RouterContext\n\tbasepath?: string\n}) {\n\treturn createRouter({\n\t\trouteTree,\n\t\thistory: input.history,\n\t\tcontext: input.context,\n\t\tbasepath: input.basepath && input.basepath !== '/' ? input.basepath : undefined,\n\t\tdefaultPreload: 'intent',\n\t\tscrollRestoration: true,\n\t})\n}\n\ndeclare module '@tanstack/react-router' {\n\tinterface Register {\n\t\trouter: ReturnType<typeof createAppRouter>\n\t}\n}\n",
	"app/router-context.ts": "import { createContext, useContext } from 'react'\nimport type { Note } from './data/notes.ts'\n\nexport type AppShellContext = {\n\tappBasePath: string\n\tassetBasePath: string\n\tclientModuleUrl: string | null\n\trunningSha: string\n\t/** Server-only: list notes from packageStorage. Undefined in the browser. */\n\tlistNotes?: () => Promise<Array<Note>>\n\t/** Server-only: format about page data. */\n\tloadAbout?: () => Promise<{\n\t\tsha: string\n\t\tshortSha: string\n\t\tmessage: string\n\t\tpublishedAt: string\n\t\tcommittedAt: string\n\t\tcodeUrl: string | null\n\t\trunningSha: string\n\t}>\n}\n\nexport const AppShellReactContext = createContext<AppShellContext | null>(null)\n\nexport function useAppShell(): AppShellContext {\n\tconst value = useContext(AppShellReactContext)\n\tif (!value) {\n\t\tthrow new Error('useAppShell must be used within AppShellReactContext')\n\t}\n\treturn value\n}\n",
	"app/router.ts": "import { packageContext } from 'kody:runtime'\nimport { createRequestId, withRequestId } from './middleware/request-id.ts'\nimport { relativePath } from './routes.ts'\nimport { icon192, icon512 } from './controllers/icons.ts'\nimport { manifest } from './controllers/manifest.ts'\nimport { version } from './controllers/version.ts'\nimport { notesAction } from './controllers/notes-action.ts'\nimport { listNotes } from './data/notes.ts'\nimport { renderApp } from './ssr.tsx'\n\n// Host strips the mount before forwarding. TanStack routes that use basepath /\n// remounted URLs need the hosted pathname, so remount here (Remix parity).\nfunction remountRequest(request: Request) {\n\tconst appBasePath = String(packageContext?.appBasePath ?? '').replace(/\\/+$/, '')\n\tif (!appBasePath) return request\n\tconst url = new URL(request.url)\n\turl.pathname = url.pathname === '/' ? appBasePath : appBasePath + url.pathname\n\treturn new Request(url, request)\n}\n\nfunction wantsJson(request: Request) {\n\tconst accept = (request.headers.get('accept') || '').toLowerCase()\n\treturn accept.startsWith('application/json')\n}\n\nasync function dispatch(request: Request): Promise<Response> {\n\tconst appBasePath = String(packageContext?.appBasePath ?? '').replace(/\\/+$/, '')\n\tconst url = new URL(request.url)\n\tconst path = relativePath(url.pathname, appBasePath)\n\tconst method = request.method.toUpperCase()\n\n\tif (path === '/health') {\n\t\treturn Response.json({ ok: true })\n\t}\n\tif (path === '/api/version') {\n\t\treturn version()\n\t}\n\tif (path === '/manifest.webmanifest') {\n\t\treturn manifest()\n\t}\n\tif (path === '/icons/icon-192.png') {\n\t\treturn icon192()\n\t}\n\tif (path === '/icons/icon-512.png') {\n\t\treturn icon512()\n\t}\n\n\tif (path === '/notes' && method === 'POST') {\n\t\treturn notesAction(request, appBasePath)\n\t}\n\n\tif (path === '/notes' && method === 'GET' && wantsJson(request)) {\n\t\tconst notes = await listNotes()\n\t\treturn Response.json({ notes })\n\t}\n\n\tif (method === 'GET' || method === 'HEAD') {\n\t\treturn renderApp(request)\n\t}\n\n\treturn new Response('Method Not Allowed', { status: 405 })\n}\n\n// Default-export { fetch } — do not export a class/router object (Worker env is not RequestInit).\nexport default {\n\tasync fetch(request: Request) {\n\t\tconst requestId = createRequestId()\n\t\ttry {\n\t\t\tconst response = await dispatch(remountRequest(request))\n\t\t\treturn withRequestId(response, requestId)\n\t\t} catch (error) {\n\t\t\tconsole.error('package app fetch failed:', error)\n\t\t\treturn withRequestId(\n\t\t\t\tResponse.json(\n\t\t\t\t\t{ ok: false, error: error instanceof Error ? error.message : String(error) },\n\t\t\t\t\t{ status: 500 },\n\t\t\t\t),\n\t\t\t\trequestId,\n\t\t\t)\n\t\t}\n\t},\n}\n",
	"app/routes.ts": "/**\n * Mount-aware path helpers (pure). Pass `appBasePath` from packageContext (Worker)\n * or `document.documentElement.dataset.appBase` (browser) — never import kody:runtime here.\n */\nexport type AppRoutes = ReturnType<typeof createRoutes>\n\nexport function createRoutes(appBasePath = '') {\n\tconst base = String(appBasePath ?? '').replace(/\\/+$/, '')\n\tconst href = (path: string) => {\n\t\tif (!path || path === '/') return base || '/'\n\t\tconst normalized = path.startsWith('/') ? path : `/${path}`\n\t\treturn `${base}${normalized}`\n\t}\n\treturn {\n\t\thome: { path: '/', href: () => href('/') },\n\t\tabout: { path: '/about', href: () => href('/about') },\n\t\tnotes: {\n\t\t\tpath: '/notes',\n\t\t\tindex: { href: () => href('/notes') },\n\t\t\taction: { href: () => href('/notes') },\n\t\t},\n\t\tmanifest: { path: '/manifest.webmanifest', href: () => href('/manifest.webmanifest') },\n\t\ticon192: { path: '/icons/icon-192.png', href: () => href('/icons/icon-192.png') },\n\t\ticon512: { path: '/icons/icon-512.png', href: () => href('/icons/icon-512.png') },\n\t\tversionApi: { path: '/api/version', href: () => href('/api/version') },\n\t\thealth: { path: '/health', href: () => href('/health') },\n\t}\n}\n\n/** Relative path under the mount (leading slash, no trailing slash except root). */\nexport function relativePath(pathname: string, appBasePath: string): string {\n\tconst base = String(appBasePath ?? '').replace(/\\/+$/, '')\n\tlet path = pathname || '/'\n\tif (base && (path === base || path.startsWith(base + '/'))) {\n\t\tpath = path.slice(base.length) || '/'\n\t}\n\tif (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)\n\treturn path || '/'\n}\n",
	"app/ssr.tsx": "import { createMemoryHistory, RouterProvider } from '@tanstack/react-router'\nimport { renderToReadableStream } from 'react-dom/server.edge'\nimport { packageContext } from 'kody:runtime'\nimport { formatAboutPageData } from 'kody:@kentcdodds/package-app-kit/about'\nimport { readRecordedVersion } from '../src/record-version.ts'\nimport { listNotes } from './data/notes.ts'\nimport { createAppRouter, type RouterContext } from './routeTree.tsx'\nimport { ErrorBanner } from './ui/error-banner.tsx'\nimport { Document } from './ui/document.tsx'\nimport { toErrorMessage } from './ui/to-error-message.ts'\n\nasync function buildContext(runningSha?: string): Promise<RouterContext> {\n\tconst version = await readRecordedVersion()\n\treturn {\n\t\tappBasePath: packageContext?.appBasePath ?? '',\n\t\tassetBasePath: packageContext?.assetBasePath ?? '',\n\t\tclientModuleUrl: packageContext?.clientModuleUrl ?? null,\n\t\trunningSha: runningSha ?? version.sha,\n\t\tlistNotes,\n\t\tasync loadAbout() {\n\t\t\tconst recorded = await readRecordedVersion()\n\t\t\tconst about = formatAboutPageData({\n\t\t\t\tversion: recorded,\n\t\t\t\trunningSha: recorded.sha,\n\t\t\t\tcodeUrl: null,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tsha: about.sha,\n\t\t\t\tshortSha: about.shortSha,\n\t\t\t\tmessage: about.message,\n\t\t\t\tpublishedAt: about.publishedAt,\n\t\t\t\tcommittedAt: about.committedAt,\n\t\t\t\tcodeUrl: about.codeUrl,\n\t\t\t\trunningSha: about.runningSha || about.sha,\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * SSR the matched TanStack route into a full HTML document Response.\n * Uses react-dom/server.edge renderToReadableStream (Workers-safe) — not TanStack Start / Vite.\n */\nexport async function renderApp(request: Request): Promise<Response> {\n\tconst url = new URL(request.url)\n\tconst context = await buildContext()\n\tconst basepath = context.appBasePath.replace(/\\/+$/, '') || undefined\n\tconst history = createMemoryHistory({\n\t\tinitialEntries: [url.pathname + url.search],\n\t})\n\tconst router = createAppRouter({ history, context, basepath })\n\n\ttry {\n\t\tawait router.load()\n\t\tconst stream = await renderToReadableStream(\n\t\t\t<RouterProvider router={router} />,\n\t\t\t{\n\t\t\t\tonError(error) {\n\t\t\t\t\tconsole.error('SSR render error:', error)\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\treturn new Response(stream, {\n\t\t\theaders: {\n\t\t\t\t'content-type': 'text/html; charset=utf-8',\n\t\t\t\t'cache-control': 'no-store',\n\t\t\t},\n\t\t})\n\t} catch (error) {\n\t\tconst message = toErrorMessage(error)\n\t\tconsole.error('SSR render failed:', error)\n\t\tconst stream = await renderToReadableStream(\n\t\t\t<Document\n\t\t\t\ttitle={`Error · __APP_TITLE__`}\n\t\t\t\tappBasePath={context.appBasePath}\n\t\t\t\tassetBasePath={context.assetBasePath}\n\t\t\t\tclientModuleUrl={context.clientModuleUrl}\n\t\t\t\trunningSha={context.runningSha}\n\t\t\t>\n\t\t\t\t<div className=\"pak-wrap pak-stack\">\n\t\t\t\t\t<ErrorBanner title=\"Something went wrong\" message={message} detail={message} />\n\t\t\t\t</div>\n\t\t\t</Document>,\n\t\t)\n\t\treturn new Response(stream, {\n\t\t\tstatus: 500,\n\t\t\theaders: {\n\t\t\t\t'content-type': 'text/html; charset=utf-8',\n\t\t\t\t'cache-control': 'no-store',\n\t\t\t},\n\t\t})\n\t}\n}\n",
	"app/ui/about-panel.tsx": "import { useEffect, useState } from 'react'\nimport {\n\tformatLocalDateTime,\n\treadLastUpdateCheckAt,\n\twriteLastUpdateCheckAt,\n} from './local-time.ts'\nimport { createBusyGate } from './busy.ts'\nimport { ErrorBanner } from './error-banner.tsx'\n\nexport type AboutPanelProps = {\n\tsha: string\n\tshortSha: string\n\tmessage: string\n\tpublishedAt: string\n\tcommittedAt: string\n\tcodeUrl: string | null\n\trunningSha: string\n\tappId?: string\n}\n\n/**\n * About metadata + manual update check.\n * Times render in the browser's local timezone; last-checked is stored locally.\n */\nexport function AboutPanel(props: AboutPanelProps) {\n\tconst appId = props.appId || '__PACKAGE_ID__'\n\tconst [lastCheckedAt, setLastCheckedAt] = useState<string | null>(null)\n\tconst [checking, setChecking] = useState(false)\n\tconst [showBusy, setShowBusy] = useState(false)\n\tconst [status, setStatus] = useState<'idle' | 'ok' | 'update' | 'error'>('idle')\n\tconst [statusDetail, setStatusDetail] = useState('')\n\n\tuseEffect(() => {\n\t\tsetLastCheckedAt(readLastUpdateCheckAt(appId))\n\t}, [appId])\n\n\tasync function runCheck() {\n\t\tif (checking || typeof window === 'undefined') return\n\t\tsetChecking(true)\n\t\tsetStatus('idle')\n\t\tsetStatusDetail('')\n\t\tconst gate = createBusyGate({ delayMs: 200, minDurationMs: 400 })\n\t\tgate.start((phase) => setShowBusy(phase === 'busy'))\n\t\ttry {\n\t\t\tconst root = document.documentElement\n\t\t\tconst appBase = root.getAttribute('data-app-base') || ''\n\t\t\tconst runningSha =\n\t\t\t\tprops.runningSha || root.getAttribute('data-running-sha') || ''\n\t\t\tconst res = await fetch(`${appBase}/api/version`, {\n\t\t\t\tcache: 'no-store',\n\t\t\t\tcredentials: 'same-origin',\n\t\t\t})\n\t\t\tconst version = res.ok\n\t\t\t\t? ((await res.json()) as { sha?: string; shortSha?: string })\n\t\t\t\t: {}\n\t\t\tlet waiting = false\n\t\t\tif ('serviceWorker' in navigator) {\n\t\t\t\tconst regs = await navigator.serviceWorker.getRegistrations()\n\t\t\t\tfor (const reg of regs) {\n\t\t\t\t\tif (reg.waiting) waiting = true\n\t\t\t\t\tif (reg.update) await reg.update().catch(() => null)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst checked = writeLastUpdateCheckAt(new Date().toISOString(), appId)\n\t\t\tsetLastCheckedAt(checked)\n\t\t\tconst latestSha = String(version.sha || '')\n\t\t\tif (waiting || (runningSha && latestSha && runningSha !== latestSha)) {\n\t\t\t\tsetStatus('update')\n\t\t\t\tsetStatusDetail(\n\t\t\t\t\twaiting ? 'Service worker waiting' : `Latest ${version.shortSha || latestSha.slice(0, 7)}`,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tsetStatus('ok')\n\t\t\t\tsetStatusDetail('Up to date')\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tsetStatus('error')\n\t\t\tsetStatusDetail(error instanceof Error ? error.message : String(error))\n\t\t\tsetLastCheckedAt(writeLastUpdateCheckAt(new Date().toISOString(), appId))\n\t\t} finally {\n\t\t\tsetChecking(false)\n\t\t\tawait gate.stop((phase) => setShowBusy(phase === 'busy'))\n\t\t}\n\t}\n\n\tconst {\n\t\tsha,\n\t\tshortSha,\n\t\tmessage,\n\t\tpublishedAt,\n\t\tcommittedAt,\n\t\tcodeUrl,\n\t} = props\n\tconst publishedLocal = formatLocalDateTime(publishedAt)\n\tconst committedLocal = formatLocalDateTime(committedAt)\n\tconst checkedLocal = formatLocalDateTime(lastCheckedAt)\n\n\treturn (\n\t\t<section className=\"pak-card pak-stack\" data-about-panel>\n\t\t\t<dl className=\"pak-stack-sm\">\n\t\t\t\t<div>\n\t\t\t\t\t<dt className=\"pak-muted\">SHA</dt>\n\t\t\t\t\t<dd>\n\t\t\t\t\t\t<code tabIndex={0} data-tip={sha || ''}>\n\t\t\t\t\t\t\t{shortSha || sha || '—'}\n\t\t\t\t\t\t</code>\n\t\t\t\t\t</dd>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<dt className=\"pak-muted\">Message</dt>\n\t\t\t\t\t<dd>{message || '—'}</dd>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<dt className=\"pak-muted\">Published</dt>\n\t\t\t\t\t<dd data-tip={publishedAt || undefined}>{publishedLocal}</dd>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<dt className=\"pak-muted\">Committed</dt>\n\t\t\t\t\t<dd data-tip={committedAt || undefined}>{committedLocal}</dd>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<dt className=\"pak-muted\">Last checked</dt>\n\t\t\t\t\t<dd data-tip={lastCheckedAt || undefined}>\n\t\t\t\t\t\t{lastCheckedAt ? checkedLocal : 'Never'}\n\t\t\t\t\t</dd>\n\t\t\t\t</div>\n\t\t\t\t{codeUrl ? (\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<dt className=\"pak-muted\">Code</dt>\n\t\t\t\t\t\t<dd>\n\t\t\t\t\t\t\t<a href={codeUrl} data-tip={codeUrl}>\n\t\t\t\t\t\t\t\tPackage\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</dd>\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t</dl>\n\t\t\t<div className=\"pak-cluster\">\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"pak-btn\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-about-check-update\n\t\t\t\t\tdisabled={checking || undefined}\n\t\t\t\t\tdata-pending={checking ? 'true' : undefined}\n\t\t\t\t\taria-busy={checking ? 'true' : undefined}\n\t\t\t\t\tonClick={() => void runCheck()}\n\t\t\t\t>\n\t\t\t\t\t{showBusy ? 'Checking…' : 'Check for updates'}\n\t\t\t\t</button>\n\t\t\t\t{status === 'ok' ? <span className=\"pak-muted\">Up to date</span> : null}\n\t\t\t\t{status === 'update' ? (\n\t\t\t\t\t<span className=\"pak-muted\">{statusDetail || 'Update available'}</span>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t{status === 'error' ? (\n\t\t\t\t<ErrorBanner title=\"Update check failed\" message={statusDetail || 'Unknown error'} />\n\t\t\t) : null}\n\t\t</section>\n\t)\n}\n",
	"app/ui/busy.ts": "/**\n * Busy / pending helpers inspired by `spin-delay`:\n * - Give immediate affordance on press (so taps never feel ignored)\n * - Delay showing a \"loading\" label/spinner so fast ops do not flash\n * - Once shown, keep it visible for a minimum duration\n */\n\nexport type BusyPhase = 'idle' | 'pending' | 'busy'\n\nexport type BusyGateOptions = {\n\t/** Wait this long before flipping pending → busy (default 200ms). */\n\tdelayMs?: number\n\t/** Once busy, stay busy at least this long (default 400ms). */\n\tminDurationMs?: number\n}\n\nexport function createBusyGate(options: BusyGateOptions = {}) {\n\tconst delayMs = options.delayMs ?? 200\n\tconst minDurationMs = options.minDurationMs ?? 400\n\tlet phase: BusyPhase = 'idle'\n\tlet delayTimer: ReturnType<typeof setTimeout> | null = null\n\tlet busySince = 0\n\tlet generation = 0\n\n\tfunction clearDelay() {\n\t\tif (delayTimer) {\n\t\t\tclearTimeout(delayTimer)\n\t\t\tdelayTimer = null\n\t\t}\n\t}\n\n\treturn {\n\t\tget phase() {\n\t\t\treturn phase\n\t\t},\n\t\t/** Call synchronously on pointer/click — marks pending immediately. */\n\t\tstart(onChange?: (phase: BusyPhase) => void) {\n\t\t\tgeneration += 1\n\t\t\tconst gen = generation\n\t\t\tclearDelay()\n\t\t\tphase = 'pending'\n\t\t\tonChange?.(phase)\n\t\t\tdelayTimer = setTimeout(() => {\n\t\t\t\tif (gen !== generation) return\n\t\t\t\tphase = 'busy'\n\t\t\t\tbusySince = Date.now()\n\t\t\t\tonChange?.(phase)\n\t\t\t}, delayMs)\n\t\t},\n\t\t/** Resolve when the async work finishes; respects min busy duration. */\n\t\tasync stop(onChange?: (phase: BusyPhase) => void) {\n\t\t\tconst gen = generation\n\t\t\tclearDelay()\n\t\t\tif (phase === 'busy') {\n\t\t\t\tconst elapsed = Date.now() - busySince\n\t\t\t\tconst wait = Math.max(0, minDurationMs - elapsed)\n\t\t\t\tif (wait > 0) await new Promise((r) => setTimeout(r, wait))\n\t\t\t}\n\t\t\tif (gen !== generation) return\n\t\t\tphase = 'idle'\n\t\t\tonChange?.(phase)\n\t\t},\n\t\t/** True while the user should see a loading treatment (after delay). */\n\t\tget showBusy() {\n\t\t\treturn phase === 'busy'\n\t\t},\n\t\t/** True from the moment of press until stop — use for disable / aria-busy. */\n\t\tget isActive() {\n\t\t\treturn phase !== 'idle'\n\t\t},\n\t}\n}\n",
	"app/ui/counter.tsx": "import { useState } from 'react'\n\n/** Interactive counter — demonstrates client React state after hydration. */\nexport function Counter({ initialCount, label }: { initialCount: number; label: string }) {\n\tconst [count, setCount] = useState(initialCount)\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName=\"pak-btn pak-btn-accent\"\n\t\t\tonClick={() => setCount((value) => value + 1)}\n\t\t>\n\t\t\t{label}: {count}\n\t\t</button>\n\t)\n}\n",
	"app/ui/document.tsx": "import type { ReactNode } from 'react'\nimport { createRoutes } from '../routes.ts'\n\nexport type DocumentProps = {\n\ttitle: string\n\tappBasePath: string\n\tassetBasePath: string\n\tclientModuleUrl: string | null\n\trunningSha?: string\n\tpage?: string\n\tchildren?: ReactNode\n}\n\nexport function Document({\n\ttitle,\n\tappBasePath,\n\tassetBasePath,\n\tclientModuleUrl,\n\trunningSha,\n\tpage,\n\tchildren,\n}: DocumentProps) {\n\tconst appBase = appBasePath.replace(/\\/$/, '')\n\tconst assetBase = assetBasePath.replace(/\\/$/, '')\n\tconst routes = createRoutes(appBase)\n\tconst icon192 = `${routes.icon192.href()}?v=4`\n\tconst manifest = routes.manifest.href()\n\tconst swUrl = assetBase ? `${assetBase}/sw.js` : `${appBase}/sw.js`\n\tconst pakConfig = JSON.stringify({\n\t\tappName: '__APP_TITLE__',\n\t\tappLabel: '__APP_TITLE__',\n\t\thintDismissKey: '__PACKAGE_ID__-install-hint-dismissed',\n\t\tversionPath: '/api/version',\n\t\tappBase,\n\t\tassetBase: assetBase || undefined,\n\t\tclientModuleUrl: clientModuleUrl || undefined,\n\t\tswUrl,\n\t})\n\n\treturn (\n\t\t<html\n\t\t\tlang=\"en\"\n\t\t\tdata-app-base={appBase}\n\t\t\tdata-asset-base={assetBase || undefined}\n\t\t\tdata-client-module={clientModuleUrl || undefined}\n\t\t\tdata-sw-url={swUrl}\n\t\t\tdata-running-sha={runningSha || ''}\n\t\t\tdata-page={page || ''}\n\t\t\tdata-app-name=\"__APP_TITLE__\"\n\t\t\tdata-pak-config={pakConfig}\n\t\t>\n\t\t\t<head>\n\t\t\t\t<meta charSet=\"utf-8\" />\n\t\t\t\t<meta\n\t\t\t\t\tname=\"viewport\"\n\t\t\t\t\tcontent=\"width=device-width, initial-scale=1, viewport-fit=cover\"\n\t\t\t\t/>\n\t\t\t\t<meta name=\"color-scheme\" content=\"dark\" />\n\t\t\t\t<meta name=\"theme-color\" content=\"#0a0a0a\" />\n\t\t\t\t<meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n\t\t\t\t<meta name=\"mobile-web-app-capable\" content=\"yes\" />\n\t\t\t\t<title>{title}</title>\n\t\t\t\t<link rel=\"manifest\" href={manifest} crossOrigin=\"use-credentials\" />\n\t\t\t\t<link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href={icon192} />\n\t\t\t\t<link rel=\"apple-touch-icon\" href={icon192} />\n\t\t\t\t<link rel=\"stylesheet\" href={`${assetBase}/styles.css`} />\n\t\t\t</head>\n\t\t\t<body>\n\t\t\t\t{children}\n\t\t\t\t<div className=\"pak-toast-host\" data-toast-host aria-live=\"polite\" />\n\t\t\t\t{clientModuleUrl ? <script type=\"module\" src={clientModuleUrl} /> : null}\n\t\t\t</body>\n\t\t</html>\n\t)\n}\n",
	"app/ui/double-check-button.tsx": "import { useState, type ReactNode } from 'react'\n\n/**\n * Two-step confirm for destructive form submits (progressive enhancement).\n * SSR / no-JS: `type=\"submit\"` so the parent form POSTs normally.\n * With JS: first click arms (preventDefault); second click submits; blur resets.\n */\nexport function DoubleCheckButton({\n\tlabel = 'Delete',\n\tconfirmLabel = 'Are you sure?',\n\tclassName = 'pak-btn pak-btn-danger',\n\ticon,\n}: {\n\tlabel?: string\n\tconfirmLabel?: string\n\tclassName?: string\n\ticon?: ReactNode\n}) {\n\tconst [armed, setArmed] = useState(false)\n\treturn (\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tclassName={className}\n\t\t\tdata-double-check\n\t\t\tdata-armed={armed ? 'true' : 'false'}\n\t\t\taria-pressed={armed ? 'true' : 'false'}\n\t\t\tonClick={(event) => {\n\t\t\t\tif (!armed) {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tsetArmed(true)\n\t\t\t\t}\n\t\t\t}}\n\t\t\tonBlur={() => {\n\t\t\t\tif (armed) setArmed(false)\n\t\t\t}}\n\t\t>\n\t\t\t{armed ? (\n\t\t\t\tconfirmLabel\n\t\t\t) : (\n\t\t\t\t<span className=\"pak-cluster pak-gap-2\">\n\t\t\t\t\t{icon}\n\t\t\t\t\t{label}\n\t\t\t\t</span>\n\t\t\t)}\n\t\t</button>\n\t)\n}\n",
	"app/ui/error-banner.tsx": "import type { ReactNode } from 'react'\n\nexport type ErrorBannerProps = {\n\ttitle?: string\n\tmessage: string\n\thint?: ReactNode\n\t/** Shown via data-tip (hover/focus) */\n\tdetail?: string\n}\n\n/**\n * Inline error alert for reserved content areas.\n * Prefer toasts for ephemeral failures (zero CLS); use this when replacing expected content.\n */\nexport function ErrorBanner({ title, message, hint, detail }: ErrorBannerProps) {\n\treturn (\n\t\t<div className=\"pak-error\" role=\"alert\" tabIndex={0} data-tip={detail || undefined}>\n\t\t\t<strong className=\"pak-error-title\">{title || 'Something went wrong'}</strong>\n\t\t\t<p className=\"pak-error-message\">{message}</p>\n\t\t\t{hint ? <p className=\"pak-muted pak-error-hint\">{hint}</p> : null}\n\t\t</div>\n\t)\n}\n",
	"app/ui/install-cta.tsx": "import { useCallback, useEffect, useState } from 'react'\nimport { IconDownload } from '../icons/download.tsx'\n\ntype DeferredPrompt = {\n\tprompt: () => Promise<void>\n\tuserChoice?: Promise<unknown>\n}\n\n/**\n * Module-level capture so remounts cannot drop a preventDefault'd BIP\n * event (Chrome warns if preventDefault runs and prompt() never does).\n */\nlet capturedPrompt: DeferredPrompt | null = null\nlet bipListenerBound = false\nconst listeners = new Set<() => void>()\n\nfunction bindBeforeInstallPrompt() {\n\tif (typeof window === 'undefined' || bipListenerBound) return\n\tbipListenerBound = true\n\twindow.addEventListener('beforeinstallprompt', (event) => {\n\t\tconst promptFn = (event as unknown as DeferredPrompt).prompt\n\t\tif (typeof promptFn !== 'function' || !document.querySelector('[data-install]')) {\n\t\t\treturn\n\t\t}\n\t\tevent.preventDefault()\n\t\tcapturedPrompt = event as unknown as DeferredPrompt\n\t\tfor (const listener of listeners) listener()\n\t})\n\twindow.addEventListener('appinstalled', () => {\n\t\tcapturedPrompt = null\n\t\tfor (const listener of listeners) listener()\n\t})\n}\n\nasync function promptCapturedInstall() {\n\tconst event = capturedPrompt\n\tif (typeof event?.prompt !== 'function') return false\n\tcapturedPrompt = null\n\ttry {\n\t\tawait event.prompt()\n\t\tif (event.userChoice) await event.userChoice\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Header install control (reserved slot) + optional iOS A2HS overlay.\n */\nexport function InstallCta({ appName }: { appName: string }) {\n\tconst [standalone, setStandalone] = useState(false)\n\tconst [ios, setIos] = useState(false)\n\tconst [hintDismissed, setHintDismissed] = useState(false)\n\tconst [iosHintOpen, setIosHintOpen] = useState(false)\n\tconst [canNative, setCanNative] = useState(false)\n\tconst [clientReady, setClientReady] = useState(false)\n\tconst hintKey = '__PACKAGE_ID__-install-hint-dismissed'\n\n\tconst detect = useCallback(() => {\n\t\ttry {\n\t\t\tsetStandalone(\n\t\t\t\t(navigator as { standalone?: boolean }).standalone === true ||\n\t\t\t\t\twindow.matchMedia(\n\t\t\t\t\t\t'(display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui)',\n\t\t\t\t\t).matches,\n\t\t\t)\n\t\t} catch {\n\t\t\tsetStandalone(false)\n\t\t}\n\t\tsetIos(\n\t\t\t/iphone|ipad|ipod/i.test(navigator.userAgent) ||\n\t\t\t\t(navigator.platform === 'MacIntel' && (navigator.maxTouchPoints || 0) > 1),\n\t\t)\n\t\ttry {\n\t\t\tsetHintDismissed(localStorage.getItem(hintKey) !== null)\n\t\t} catch {\n\t\t\t/* ignore */\n\t\t}\n\t\tsetCanNative(Boolean(capturedPrompt?.prompt))\n\t}, [])\n\n\tuseEffect(() => {\n\t\tsetClientReady(true)\n\t\tdetect()\n\t\tbindBeforeInstallPrompt()\n\t\tconst onChange = () => detect()\n\t\tlisteners.add(onChange)\n\t\treturn () => {\n\t\t\tlisteners.delete(onChange)\n\t\t}\n\t}, [detect])\n\n\tconst showIos = clientReady && !standalone && ios && !hintDismissed && !canNative\n\tconst installAvailable = canNative || showIos\n\tconst tip = `Install ${appName}`\n\n\treturn (\n\t\t<span className=\"pak-install-root\">\n\t\t\t<div\n\t\t\t\tclassName=\"pak-install-slot\"\n\t\t\t\tdata-install-wrap\n\t\t\t\tdata-available={installAvailable ? 'true' : 'false'}\n\t\t\t>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"pak-btn pak-btn-icon\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-install\n\t\t\t\t\tdata-tip={tip}\n\t\t\t\t\taria-label={tip}\n\t\t\t\t\taria-hidden={installAvailable ? undefined : true}\n\t\t\t\t\ttabIndex={installAvailable ? undefined : -1}\n\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\tif (capturedPrompt?.prompt) {\n\t\t\t\t\t\t\tawait promptCapturedInstall()\n\t\t\t\t\t\t\tdetect()\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (showIos) setIosHintOpen((open) => !open)\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<IconDownload size={20} />\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t{showIos && iosHintOpen ? (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"pak-install-hint-overlay\"\n\t\t\t\t\tdata-install-hint\n\t\t\t\t\trole=\"dialog\"\n\t\t\t\t\taria-label=\"Add to Home Screen\"\n\t\t\t\t>\n\t\t\t\t\t<div className=\"pak-card pak-stack pak-install-hint-card\">\n\t\t\t\t\t\t<p className=\"pak-muted\">\n\t\t\t\t\t\t\tAdd <strong>{appName}</strong> to your Home Screen: tap <strong>Share</strong>, then{' '}\n\t\t\t\t\t\t\t<strong>Add to Home Screen</strong>.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"pak-btn\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tdata-install-hint-dismiss\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tlocalStorage.setItem(hintKey, String(Date.now()))\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t/* ignore */\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsetHintDismissed(true)\n\t\t\t\t\t\t\t\tsetIosHintOpen(false)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tDismiss\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</span>\n\t)\n}\n",
	"app/ui/layout.tsx": "import type { ReactNode } from 'react'\nimport { Link } from '@tanstack/react-router'\nimport { InstallCta } from './install-cta.tsx'\nimport { UpdateBanner } from './update-banner.tsx'\nimport { createRoutes } from '../routes.ts'\nimport { useAppShell } from '../router-context.ts'\n\nexport type AppShellProps = {\n\tpage: 'home' | 'about' | 'notes'\n\theading: string\n\tlede?: ReactNode\n\tchildren?: ReactNode\n}\n\n/**\n * Tooltips (`data-tip`): tip only controls that are NOT obvious from visible\n * label/text. Labeled nav and action buttons do not get tips.\n */\nexport function AppShell({ page, heading, lede, children }: AppShellProps) {\n\tconst { appBasePath } = useAppShell()\n\tconst routes = createRoutes(appBasePath)\n\treturn (\n\t\t<div className=\"pak-wrap pak-stack\">\n\t\t\t<header className=\"pak-header\">\n\t\t\t\t<div className=\"pak-brand\">\n\t\t\t\t\t<img\n\t\t\t\t\t\tclassName=\"pak-logo\"\n\t\t\t\t\t\tsrc={routes.icon192.href()}\n\t\t\t\t\t\twidth={28}\n\t\t\t\t\t\theight={28}\n\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\tdecoding=\"async\"\n\t\t\t\t\t\ttabIndex={0}\n\t\t\t\t\t\tdata-tip=\"__PACKAGE_NAME__\"\n\t\t\t\t\t\taria-label=\"__PACKAGE_NAME__\"\n\t\t\t\t\t/>\n\t\t\t\t\t<h1 className=\"pak-title\">{heading}</h1>\n\t\t\t\t\t<InstallCta appName=\"__APP_TITLE__\" />\n\t\t\t\t</div>\n\t\t\t\t<nav className=\"pak-nav\" aria-label=\"App\">\n\t\t\t\t\t<Link\n\t\t\t\t\t\tto=\"/\"\n\t\t\t\t\t\tclassName=\"pak-btn\"\n\t\t\t\t\t\taria-current={page === 'home' ? 'page' : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\tHome\n\t\t\t\t\t</Link>\n\t\t\t\t\t<Link\n\t\t\t\t\t\tto=\"/notes\"\n\t\t\t\t\t\tclassName=\"pak-btn\"\n\t\t\t\t\t\taria-current={page === 'notes' ? 'page' : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\tNotes\n\t\t\t\t\t</Link>\n\t\t\t\t\t<Link\n\t\t\t\t\t\tto=\"/about\"\n\t\t\t\t\t\tclassName=\"pak-btn\"\n\t\t\t\t\t\taria-current={page === 'about' ? 'page' : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\tAbout\n\t\t\t\t\t</Link>\n\t\t\t\t</nav>\n\t\t\t\t{lede ? <p className=\"pak-muted pak-lede\">{lede}</p> : null}\n\t\t\t</header>\n\t\t\t<UpdateBanner />\n\t\t\t{children}\n\t\t</div>\n\t)\n}\n",
	"app/ui/local-time.ts": "/** Format an ISO timestamp in the browser's local timezone. */\nexport function formatLocalDateTime(iso: string | null | undefined): string {\n\tconst raw = String(iso || '').trim()\n\tif (!raw) return '—'\n\tconst ms = Date.parse(raw)\n\tif (!Number.isFinite(ms)) return '—'\n\ttry {\n\t\treturn new Intl.DateTimeFormat(undefined, {\n\t\t\tdateStyle: 'medium',\n\t\t\ttimeStyle: 'short',\n\t\t}).format(new Date(ms))\n\t} catch {\n\t\treturn new Date(ms).toLocaleString()\n\t}\n}\n\nexport function lastUpdateCheckStorageKey(appId = 'package-app-kit-demo'): string {\n\treturn `${appId}-last-update-check`\n}\n\nexport function readLastUpdateCheckAt(appId?: string): string | null {\n\tif (typeof localStorage === 'undefined') return null\n\ttry {\n\t\tconst value = localStorage.getItem(lastUpdateCheckStorageKey(appId))\n\t\treturn value && Date.parse(value) ? value : null\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport function writeLastUpdateCheckAt(iso = new Date().toISOString(), appId?: string): string {\n\ttry {\n\t\tlocalStorage.setItem(lastUpdateCheckStorageKey(appId), iso)\n\t} catch {\n\t\t/* ignore */\n\t}\n\treturn iso\n}\n",
	"app/ui/notes-demo.tsx": "import { useEffect, useRef, useState, type FormEvent } from 'react'\nimport { DoubleCheckButton } from './double-check-button.tsx'\nimport { IconTrash2 } from '../icons/trash-2.tsx'\nimport { IconPlus } from '../icons/plus.tsx'\nimport { createBusyGate } from './busy.ts'\n\nexport type NotesDemoNote = {\n\tid: string\n\ttext: string\n\tcreatedAt: string\n}\n\nexport type NotesDemoProps = {\n\tnotes: Array<NotesDemoNote>\n\tactionHref: string\n}\n\ntype Row = NotesDemoNote & { optimistic?: boolean }\n\n/**\n * Notes list + add form as a progressive-enhancement island.\n * Without JS: native POST forms + 303 redirect. With JS: optimistic add/delete.\n */\nexport function NotesDemo({ notes, actionHref }: NotesDemoProps) {\n\tconst [rows, setRows] = useState<Array<Row>>(() => notes.map((n) => ({ ...n })))\n\tconst [showBusy, setShowBusy] = useState(false)\n\tconst [adding, setAdding] = useState(false)\n\tconst deletePending = useRef(new Set<string>())\n\tconst addPending = useRef(new Set<string>())\n\tconst addBusy = useRef(createBusyGate({ delayMs: 200, minDurationMs: 400 }))\n\tconst propsKey = useRef(JSON.stringify(notes))\n\n\tuseEffect(() => {\n\t\tconst nextKey = JSON.stringify(notes)\n\t\tif (nextKey === propsKey.current) return\n\t\tif (deletePending.current.size > 0 || addPending.current.size > 0) return\n\t\tpropsKey.current = nextKey\n\t\tsetRows(notes.map((n) => ({ ...n })))\n\t}, [notes])\n\n\tfunction focusAddInput() {\n\t\tconst el = document.querySelector<HTMLInputElement>(\n\t\t\t'[data-notes-demo] input[name=\"text\"]',\n\t\t)\n\t\tel?.focus()\n\t}\n\n\tasync function postAction(formData: FormData) {\n\t\tconst res = await fetch(actionHref, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: formData,\n\t\t\tcredentials: 'same-origin',\n\t\t\theaders: { Accept: 'application/json' },\n\t\t})\n\t\tlet data: unknown = null\n\t\ttry {\n\t\t\tdata = await res.json()\n\t\t} catch {\n\t\t\tdata = null\n\t\t}\n\t\treturn { ok: res.ok, data }\n\t}\n\n\tasync function onAddSubmit(event: FormEvent<HTMLFormElement>) {\n\t\tevent.preventDefault()\n\t\tconst form = event.currentTarget\n\t\tconst formData = new FormData(form)\n\t\tformData.set('intent', 'add')\n\t\tconst text = String(formData.get('text') || '').trim()\n\t\tif (!text) return\n\n\t\tconst tempId = `optimistic-${crypto.randomUUID()}`\n\t\tconst createdAt = new Date().toISOString()\n\t\tconst wasIdle = addPending.current.size === 0\n\t\taddPending.current.add(tempId)\n\t\tsetRows((prev) => [{ id: tempId, text, createdAt, optimistic: true }, ...prev])\n\t\tform.reset()\n\t\tif (wasIdle) {\n\t\t\tsetAdding(true)\n\t\t\taddBusy.current.start((phase) => setShowBusy(phase === 'busy'))\n\t\t}\n\t\tfocusAddInput()\n\n\t\ttry {\n\t\t\tconst { ok, data } = await postAction(formData)\n\t\t\tconst note =\n\t\t\t\tdata && typeof data === 'object' && data !== null && 'note' in data\n\t\t\t\t\t? (data as { note?: NotesDemoNote }).note\n\t\t\t\t\t: null\n\t\t\tif (!ok || !note?.id) throw new Error('add failed')\n\t\t\tsetRows((prev) =>\n\t\t\t\tprev.map((row) =>\n\t\t\t\t\trow.id === tempId\n\t\t\t\t\t\t? { id: note.id, text: note.text, createdAt: note.createdAt }\n\t\t\t\t\t\t: row,\n\t\t\t\t),\n\t\t\t)\n\t\t} catch {\n\t\t\tsetRows((prev) => prev.filter((row) => row.id !== tempId))\n\t\t\tconst input = form.querySelector<HTMLInputElement>('input[name=\"text\"]')\n\t\t\tif (input && !input.value) input.value = text\n\t\t} finally {\n\t\t\taddPending.current.delete(tempId)\n\t\t\tif (addPending.current.size === 0) {\n\t\t\t\tawait addBusy.current.stop((phase) => setShowBusy(phase === 'busy'))\n\t\t\t\tsetAdding(false)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function onDeleteSubmit(event: FormEvent<HTMLFormElement>, noteId: string) {\n\t\tevent.preventDefault()\n\t\tif (deletePending.current.has(noteId)) return\n\t\tconst form = event.currentTarget\n\t\tconst formData = new FormData(form)\n\t\tformData.set('intent', 'delete')\n\t\tformData.set('id', noteId)\n\n\t\tlet removed: Row | undefined\n\t\tlet index = -1\n\t\tsetRows((prev) => {\n\t\t\tindex = prev.findIndex((row) => row.id === noteId)\n\t\t\tif (index < 0) return prev\n\t\t\tremoved = prev[index]\n\t\t\tdeletePending.current.add(noteId)\n\t\t\treturn prev.filter((row) => row.id !== noteId)\n\t\t})\n\t\tif (!removed) return\n\n\t\ttry {\n\t\t\tconst { ok } = await postAction(formData)\n\t\t\tif (!ok) throw new Error('delete failed')\n\t\t} catch {\n\t\t\tsetRows((prev) => {\n\t\t\t\tconst copy = [...prev]\n\t\t\t\tcopy.splice(index, 0, removed!)\n\t\t\t\treturn copy\n\t\t\t})\n\t\t} finally {\n\t\t\tdeletePending.current.delete(noteId)\n\t\t}\n\t}\n\n\tconst addLabel = showBusy ? 'Adding…' : 'Add'\n\n\treturn (\n\t\t<section className=\"pak-card pak-stack\" data-notes-demo>\n\t\t\t<form\n\t\t\t\tmethod=\"post\"\n\t\t\t\taction={actionHref}\n\t\t\t\tclassName=\"pak-row\"\n\t\t\t\tonSubmit={(event) => void onAddSubmit(event)}\n\t\t\t>\n\t\t\t\t<input type=\"hidden\" name=\"intent\" value=\"add\" />\n\t\t\t\t<label className=\"pak-grow\" style={{ flex: '1 1 180px' }}>\n\t\t\t\t\t<span className=\"visually-hidden\">New note</span>\n\t\t\t\t\t<input\n\t\t\t\t\t\tclassName=\"pak-input\"\n\t\t\t\t\t\tname=\"text\"\n\t\t\t\t\t\tmaxLength={120}\n\t\t\t\t\t\tplaceholder=\"Write a note…\"\n\t\t\t\t\t\trequired\n\t\t\t\t\t\taria-label=\"New note\"\n\t\t\t\t\t/>\n\t\t\t\t</label>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"pak-btn pak-btn-accent\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\tstyle={{ flex: '0 0 auto' }}\n\t\t\t\t\tdata-pending={adding ? 'true' : undefined}\n\t\t\t\t\taria-busy={adding ? 'true' : undefined}\n\t\t\t\t>\n\t\t\t\t\t<span className=\"pak-cluster pak-gap-2\">\n\t\t\t\t\t\t<IconPlus size={18} />\n\t\t\t\t\t\t{addLabel}\n\t\t\t\t\t</span>\n\t\t\t\t</button>\n\t\t\t</form>\n\n\t\t\t<ul className=\"pak-list\" id=\"notes\" aria-live=\"polite\">\n\t\t\t\t{rows.length === 0 ? (\n\t\t\t\t\t<li className=\"pak-list-item\">\n\t\t\t\t\t\t<span className=\"pak-muted\">No notes yet</span>\n\t\t\t\t\t</li>\n\t\t\t\t) : (\n\t\t\t\t\trows.map((note) => {\n\t\t\t\t\t\tconst pendingAdd = Boolean(note.optimistic)\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\tclassName=\"pak-list-item\"\n\t\t\t\t\t\t\t\tkey={note.id}\n\t\t\t\t\t\t\t\tdata-note-id={note.id}\n\t\t\t\t\t\t\t\tdata-pending={pendingAdd ? 'true' : undefined}\n\t\t\t\t\t\t\t\taria-busy={pendingAdd ? 'true' : undefined}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<strong>{note.text}</strong>\n\t\t\t\t\t\t\t\t\t{pendingAdd ? (\n\t\t\t\t\t\t\t\t\t\t<span className=\"pak-pending-label\"> Saving…</span>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t<div className=\"pak-muted\" style={{ fontSize: '0.8rem' }}>\n\t\t\t\t\t\t\t\t\t\t{note.createdAt}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t{pendingAdd ? (\n\t\t\t\t\t\t\t\t\t<span className=\"pak-muted\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<form\n\t\t\t\t\t\t\t\t\t\tmethod=\"post\"\n\t\t\t\t\t\t\t\t\t\taction={actionHref}\n\t\t\t\t\t\t\t\t\t\tonSubmit={(event) => void onDeleteSubmit(event, note.id)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<input type=\"hidden\" name=\"intent\" value=\"delete\" />\n\t\t\t\t\t\t\t\t\t\t<input type=\"hidden\" name=\"id\" value={note.id} />\n\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\tlabel=\"Delete\"\n\t\t\t\t\t\t\t\t\t\t\tconfirmLabel=\"Are you sure?\"\n\t\t\t\t\t\t\t\t\t\t\ticon={<IconTrash2 size={18} />}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</form>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t)\n\t\t\t\t\t})\n\t\t\t\t)}\n\t\t\t</ul>\n\t\t</section>\n\t)\n}\n",
	"app/ui/to-error-message.ts": "export function toErrorMessage(error: unknown): string {\n\tif (error instanceof Error && error.message) return error.message\n\tif (typeof error === 'string' && error.trim()) return error.trim()\n\ttry {\n\t\treturn JSON.stringify(error)\n\t} catch {\n\t\treturn String(error)\n\t}\n}\n",
	"app/ui/update-banner.tsx": "import { useEffect, useState } from 'react'\nimport { writeLastUpdateCheckAt } from './local-time.ts'\nimport { createBusyGate } from './busy.ts'\n\n/**\n * Update check as a fixed dismissible toast — never inserts into document flow\n * (avoids content layout shift).\n */\nexport function UpdateBanner() {\n\tconst [available, setAvailable] = useState<string | null>(null)\n\tconst [dismissed, setDismissed] = useState(false)\n\tconst [waitingWorker, setWaitingWorker] = useState<ServiceWorker | null>(null)\n\tconst [refreshing, setRefreshing] = useState(false)\n\tconst [showBusy, setShowBusy] = useState(false)\n\tconst appId = '__PACKAGE_ID__'\n\n\tfunction dismissKey(token: string) {\n\t\treturn `${appId}-update-dismissed-${token}`\n\t}\n\n\tfunction isDismissed(token: string) {\n\t\ttry {\n\t\t\treturn localStorage.getItem(dismissKey(token)) !== null\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfunction markDismissed(token: string) {\n\t\ttry {\n\t\t\tlocalStorage.setItem(dismissKey(token), String(Date.now()))\n\t\t} catch {\n\t\t\t/* ignore */\n\t\t}\n\t}\n\n\tasync function check() {\n\t\tif (typeof window === 'undefined') return\n\t\ttry {\n\t\t\tconst root = document.documentElement\n\t\t\tconst appBase = root.getAttribute('data-app-base') || ''\n\t\t\tconst runningSha = root.getAttribute('data-running-sha') || ''\n\t\t\tconst versionPath = `${appBase}/api/version`\n\t\t\tconst res = await fetch(versionPath, { cache: 'no-store', credentials: 'same-origin' })\n\t\t\tconst version = res.ok ? ((await res.json()) as { sha?: string; shortSha?: string }) : {}\n\t\t\tlet waiting: ServiceWorker | null = null\n\t\t\tif ('serviceWorker' in navigator) {\n\t\t\t\tconst regs = await navigator.serviceWorker.getRegistrations()\n\t\t\t\tfor (const reg of regs) {\n\t\t\t\t\tif (reg.waiting) waiting = reg.waiting\n\t\t\t\t\tif (reg.update) await reg.update().catch(() => null)\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetWaitingWorker(waiting)\n\t\t\tlet next: string | null = null\n\t\t\tif (waiting) next = 'worker'\n\t\t\telse if (runningSha && version.sha && runningSha !== version.sha) {\n\t\t\t\tnext = version.shortSha || String(version.sha).slice(0, 7)\n\t\t\t}\n\t\t\tsetAvailable(next)\n\t\t\tsetDismissed(next ? isDismissed(next) : false)\n\t\t\twriteLastUpdateCheckAt(new Date().toISOString(), appId)\n\t\t} catch {\n\t\t\t/* ignore */\n\t\t}\n\t}\n\n\tuseEffect(() => {\n\t\tvoid check()\n\t\tconst onVis = () => {\n\t\t\tif (document.visibilityState === 'visible') void check()\n\t\t}\n\t\tdocument.addEventListener('visibilitychange', onVis)\n\t\treturn () => document.removeEventListener('visibilitychange', onVis)\n\t}, [])\n\n\tconst show = Boolean(available) && !dismissed\n\tif (!show) {\n\t\treturn <span hidden data-update-idle aria-hidden=\"true\" />\n\t}\n\n\tconst label = available === 'worker' ? 'Update ready' : `Update available (${available})`\n\tconst refreshLabel = showBusy || refreshing ? 'Refreshing…' : 'Refresh'\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"pak-toast pak-update-toast\"\n\t\t\tdata-update-toast\n\t\t\tdata-tone=\"default\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t>\n\t\t\t<div>\n\t\t\t\t<strong>{label}</strong>\n\t\t\t\t<p className=\"pak-muted\">Refresh when you are ready</p>\n\t\t\t</div>\n\t\t\t<div className=\"pak-toast-actions\">\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"pak-btn pak-btn-accent\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-pending={refreshing ? 'true' : undefined}\n\t\t\t\t\taria-busy={refreshing ? 'true' : undefined}\n\t\t\t\t\tdisabled={refreshing || undefined}\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tif (refreshing) return\n\t\t\t\t\t\tconst gate = createBusyGate({ delayMs: 120, minDurationMs: 400 })\n\t\t\t\t\t\tsetRefreshing(true)\n\t\t\t\t\t\tgate.start((phase) => setShowBusy(phase === 'busy'))\n\t\t\t\t\t\tif (waitingWorker) waitingWorker.postMessage('SKIP_WAITING')\n\t\t\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\t\t\tlocation.reload()\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{refreshLabel}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\tclassName=\"pak-btn\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-tip=\"Dismiss until the next version\"\n\t\t\t\t\taria-label=\"Dismiss update\"\n\t\t\t\t\tdisabled={refreshing || undefined}\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tif (available) markDismissed(available)\n\t\t\t\t\t\tsetDismissed(true)\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tDismiss\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n",
	"fixtures/event.example.json": "{\n\t\"example\": true,\n\t\"message\": \"Replace with the event payload your subscription expects.\"\n}\n",
	"fixtures/webhook.example.json": "{\n\t\"example\": true,\n\t\"message\": \"Replace with the payload your webhook expects.\"\n}\n",
	"package.template.json": "{\n  \"name\": \"__PACKAGE_NAME__\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./record-version\": \"./src/record-version.ts\"\n  },\n  \"engines\": {\n    \"node\": \">=24\"\n  },\n  \"scripts\": {\n    \"setup\": \"kody login && kody auth setup-git\",\n    \"dev\": \"kody dev --target mock\",\n    \"dev:prod\": \"kody dev --target prod\",\n    \"fetch\": \"kody dev fetch\",\n    \"job\": \"kody dev job\",\n    \"webhook\": \"kody dev webhook\",\n    \"emit\": \"kody dev emit\",\n    \"check\": \"kody dev check\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"test\": \"node --test \\\"test/**/*.test.ts\\\"\",\n    \"prepublish:check\": \"node scripts/prepublish-check.mjs\",\n    \"publish:kody\": \"npm test && node scripts/prepublish-check.mjs && node scripts/publish.mjs\"\n  },\n  \"dependencies\": {\n    \"@tanstack/react-router\": \"1.170.38\",\n    \"react\": \"19.3.0\",\n    \"react-dom\": \"19.3.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@kodycodes/cli\": \"^1.23.0\",\n    \"@types/node\": \"^24.0.0\",\n    \"typescript\": \"^5.9.0\"\n  },\n  \"kody\": {\n    \"id\": \"__PACKAGE_ID__\",\n    \"description\": \"__KODY_DESCRIPTION__\",\n    \"searchText\": \"package app pwa tanstack react router starter kit install about update notes\",\n    \"tags\": [\n      \"app\",\n      \"pwa\",\n      \"tanstack\",\n      \"react\"\n    ],\n    \"category\": \"apps\",\n    \"app\": {\n      \"entry\": \"./app/router.ts\",\n      \"client\": \"./app/assets/entry.ts\",\n      \"assets\": \"./public\"\n    },\n    \"dependencies\": {\n      \"@kentcdodds/package-app-kit\": \"*\"\n    }\n  }\n}\n",
	"public/styles.css": "/* package-app-kit TanStack starter tokens + layout (static copy; no Vite) */\n:root {\n  color-scheme: light dark;\n  --pak-bg0: #fafafa;\n  --pak-bg1: #f4f4f5;\n  --pak-surface: #ffffff;\n  --pak-surface-2: #f4f4f5;\n  --pak-ink: #18181b;\n  --pak-muted: #71717a;\n  --pak-line: rgba(24, 24, 27, 0.12);\n  --pak-accent: #0891b2;\n  --pak-accent-2: #06b6d4;\n  --pak-accent-ink: #ffffff;\n  --pak-stripe-cyan: #06b6d4;\n  --pak-stripe-green: #16a34a;\n  --pak-stripe-yellow: #ca8a04;\n  --pak-stripe-magenta: #db2777;\n  --pak-stripe-red: #dc2626;\n  --pak-good: #16a34a;\n  --pak-warn: #ca8a04;\n  --pak-bad: #dc2626;\n  --pak-radius: 12px;\n  --pak-radius-sm: 10px;\n  --pak-tap: 48px;\n  --pak-gap: 12px;\n  --pak-space-1: 4px;\n  --pak-space-2: 8px;\n  --pak-space-3: 12px;\n  --pak-space-4: 16px;\n  --pak-space-5: 24px;\n  --pak-space-6: 32px;\n  --pak-space-7: 48px;\n  --pak-space-8: 64px;\n  --bg0: var(--pak-bg0);\n  --bg1: var(--pak-bg1);\n  --surface: var(--pak-surface);\n  --surface-2: var(--pak-surface-2);\n  --ink: var(--pak-ink);\n  --muted: var(--pak-muted);\n  --line: var(--pak-line);\n  --accent: var(--pak-accent);\n  --accent-2: var(--pak-accent-2);\n  --accent-ink: var(--pak-accent-ink);\n  --good: var(--pak-good);\n  --warn: var(--pak-warn);\n  --bad: var(--pak-bad);\n  --radius: var(--pak-radius);\n  --radius-sm: var(--pak-radius-sm);\n  --tap: var(--pak-tap);\n  --gap: var(--pak-gap);\n  --space-1: var(--pak-space-1);\n  --space-2: var(--pak-space-2);\n  --space-3: var(--pak-space-3);\n  --space-4: var(--pak-space-4);\n  --space-5: var(--pak-space-5);\n  --space-6: var(--pak-space-6);\n  --space-7: var(--pak-space-7);\n  --space-8: var(--pak-space-8);\n}\n@media (prefers-color-scheme: dark) {\n  :root {\n    --pak-bg0: #0a0a0a;\n    --pak-bg1: #121212;\n    --pak-surface: #161616;\n    --pak-surface-2: #1c1c1c;\n    --pak-ink: #f5f5f5;\n    --pak-muted: #a3a3a3;\n    --pak-line: rgba(245, 245, 245, 0.12);\n    --pak-accent: #3bffff;\n    --pak-accent-2: #22d3ee;\n    --pak-accent-ink: #0a0a0a;\n    --pak-stripe-cyan: #3bffff;\n    --pak-stripe-green: #00e887;\n    --pak-stripe-yellow: #ffe600;\n    --pak-stripe-magenta: #ff2bd6;\n    --pak-stripe-red: #ff2a2a;\n    --pak-good: #00e887;\n    --pak-warn: #ffe600;\n    --pak-bad: #ff2a2a;\n  }\n}\n* { box-sizing: border-box; }\nhtml, body { margin: 0; min-height: 100%; }\nbody {\n  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n  background: linear-gradient(160deg, var(--bg0), var(--bg1));\n  color: var(--ink);\n  line-height: 1.45;\n}\na { color: var(--accent-2); }\ncode { font-size: 0.9em; }\n.pak-wrap { max-width: 42rem; margin: 0 auto; padding: var(--space-5) var(--space-5) var(--space-8); }\n.pak-stack { display: flex; flex-direction: column; gap: var(--space-5); }\n.pak-stack-sm { display: flex; flex-direction: column; gap: var(--space-2); }\n.pak-cluster { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }\n.pak-row { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: end; }\n.pak-inset { padding: var(--space-4); }\n.pak-mt-1 { margin-top: var(--space-1); }\n.pak-mt-2 { margin-top: var(--space-2); }\n.pak-gap-2 { gap: var(--space-2); }\n.pak-card {\n  background: var(--surface);\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: var(--space-5);\n}\n.pak-eyebrow { margin: 0; font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }\n.pak-muted { color: var(--muted); margin: 0; }\n.pak-nav { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 0; }\n.pak-btn {\n  display: inline-flex; align-items: center; justify-content: center;\n  min-height: var(--tap); padding: 0 var(--space-4);\n  border-radius: var(--radius-sm); border: 1px solid var(--line);\n  background: var(--surface-2); color: var(--ink); font: inherit; text-decoration: none; cursor: pointer;\n}\n.pak-btn[aria-current=\"page\"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }\n.pak-btn-accent { background: var(--accent); border-color: transparent; color: var(--accent-ink); }\n.pak-btn-danger { background: transparent; border-color: var(--bad); color: var(--bad); }\n.pak-btn-danger[data-armed=\"true\"] { background: var(--bad); color: #fff; }\n.pak-btn:disabled { opacity: 0.55; cursor: not-allowed; }\n.pak-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }\n.pak-list-item {\n  display: flex; justify-content: space-between; gap: var(--space-3); align-items: center;\n  padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2);\n}\n\n.pak-list-item[data-pending=\"true\"] {\n  opacity: 0.62;\n  border-style: dashed;\n}\n.pak-list-item[data-pending=\"true\"] .pak-pending-label {\n  display: inline;\n  color: var(--muted);\n  font-size: 0.8rem;\n  font-weight: 650;\n}\n.pak-pending-label { display: none; }\n.pak-input {\n  display: block; width: 100%; min-height: var(--tap); margin-top: var(--space-1);\n  padding: 0 var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  font: inherit; background: var(--surface); color: var(--ink);\n}\n.pak-swatches { display: flex; flex-wrap: wrap; gap: var(--space-3); }\n.pak-swatch { display: inline-block; width: 1.25rem; height: 1.25rem; border-radius: 999px; border: 1px solid var(--line); vertical-align: middle; margin-right: var(--space-1); }\n.pak-swatch-label { display: inline-flex; align-items: center; font-size: 0.85rem; color: var(--muted); }\n.pak-toast-host {\n  position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 40;\n  display: flex; flex-direction: column; gap: var(--space-2); max-width: min(22rem, calc(100vw - 2rem));\n}\n.pak-toast {\n  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);\n  padding: var(--space-3); box-shadow: 0 8px 24px rgba(0,0,0,0.12);\n}\n.pak-toast[data-tone=\"success\"] { border-color: var(--good); }\n.pak-toast[data-tone=\"error\"] { border-color: var(--bad); }\n.pak-toast-actions { display: flex; gap: var(--space-2); margin-top: var(--space-2); }\n[hidden] { display: none !important; }\n\n/* Hide install / first-run chrome when already installed (CSS before JS hydrates). */\n@media (display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui) {\n  [data-install-wrap], [data-install-hint], [data-first-run-splash],\n  .pak-splash-overlay, .pak-install-hint-overlay { display: none !important; }\n}\n\n.visually-hidden {\n  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;\n}\n.pak-header { display: flex; flex-direction: column; gap: var(--space-4); }\n.pak-brand { display: flex; align-items: center; gap: var(--space-3); min-height: var(--tap); }\n.pak-logo {\n  width: 28px; height: 28px; border-radius: 8px; flex: 0 0 auto;\n  border: 1px solid var(--line); background: transparent; object-fit: cover;\n}\n.pak-title { margin: 0; font-size: clamp(1.2rem, 2.5vw, 1.55rem); line-height: 1.2; flex: 1 1 auto; min-width: 0; }\n.pak-lede { margin: 0; }\n.pak-grow { display: block; }\n/* Mobile-friendly tips: hover + keyboard/tap focus (no hover-only). */\n[data-tip] { position: relative; }\n[data-tip]::after {\n  content: attr(data-tip);\n  position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translateX(-50%);\n  z-index: 30; max-width: min(16rem, 70vw); padding: 0.4rem 0.55rem;\n  border-radius: 8px; border: 1px solid var(--line); background: var(--ink); color: var(--surface);\n  font-size: 0.75rem; line-height: 1.3; white-space: normal; text-align: center;\n  opacity: 0; pointer-events: none; transition: opacity 0.12s ease;\n}\n[data-tip]:hover::after,\n[data-tip]:focus::after,\n[data-tip]:focus-within::after,\n[data-tip]:focus-visible::after { opacity: 1; }\n\n\n.pak-pill {\n  display: inline-flex; align-items: center; justify-content: center;\n  min-width: 1.5rem; padding: 0 0.45rem; margin-left: var(--space-2);\n  border-radius: 999px; font-size: 0.8rem; font-weight: 650;\n  background: color-mix(in srgb, var(--accent-ink) 18%, transparent);\n  color: inherit;\n}\n.pak-btn-icon {\n  width: var(--tap); min-width: var(--tap); padding: 0;\n  flex: 0 0 auto;\n}\n.pak-install-root { display: contents; }\n.pak-install-slot {\n  width: var(--tap); height: var(--tap);\n  flex: 0 0 auto;\n  margin-left: auto;\n  display: inline-flex; align-items: center; justify-content: center;\n}\n/* Reserve space always; hide control without collapsing the slot (no CLS). */\n.pak-install-slot[data-available=\"false\"] .pak-btn-icon {\n  visibility: hidden;\n  pointer-events: none;\n}\n.pak-splash-overlay,\n.pak-install-hint-overlay {\n  position: fixed;\n  z-index: 70;\n  left: 50%;\n  top: calc(12px + env(safe-area-inset-top, 0px));\n  transform: translateX(-50%);\n  width: min(420px, calc(100% - 24px));\n  pointer-events: none;\n}\n.pak-splash-card,\n.pak-install-hint-card {\n  pointer-events: auto;\n  box-shadow: 0 10px 28px rgba(0,0,0,0.18);\n}\n.pak-install-hint-overlay {\n  top: auto;\n  bottom: calc(16px + env(safe-area-inset-bottom, 0px));\n}\n/* Author CSS must not defeat the HTML hidden attribute (display:grid/flex overlays). */\n.pak-splash-overlay[hidden],\n.pak-install-hint-overlay[hidden],\n[data-install-hint][hidden],\n[data-first-run-splash][hidden] {\n  display: none !important;\n}\n\n/* Update toast: fixed overlay — never participates in document flow (no CLS). */\n.pak-update-toast {\n  position: fixed;\n  z-index: 80;\n  left: 50%;\n  bottom: calc(16px + env(safe-area-inset-bottom, 0px));\n  transform: translateX(-50%);\n  width: min(420px, calc(100% - 24px));\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: var(--space-2) var(--space-3);\n  align-items: center;\n  margin: 0;\n  pointer-events: auto;\n  box-shadow: 0 10px 28px rgba(0,0,0,0.18);\n}\n.pak-update-toast .pak-toast-actions { margin-top: 0; flex-wrap: wrap; justify-content: flex-end; }\n.pak-update-toast p { margin: 2px 0 0; }\n\n/* Immediate press feedback without CLS — opacity/cursor only, no size change. */\n.pak-btn[data-pending=\"true\"],\n.pak-btn[aria-busy=\"true\"] {\n  opacity: 0.72;\n  cursor: progress;\n}\n\n/* Error alert — reserved in-flow slot; size stays stable (no late expansion tricks). */\n.pak-error {\n  border: 1px solid color-mix(in srgb, var(--bad) 45%, var(--line));\n  background: color-mix(in srgb, var(--bad) 8%, var(--surface));\n  border-radius: var(--radius);\n  padding: var(--space-3) var(--space-4);\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-1);\n}\n.pak-error-title { font-size: 0.95rem; color: var(--bad); }\n.pak-error-message { margin: 0; }\n.pak-error-hint { margin: 0; font-size: 0.85rem; }\n\n.pak-hero {\n  display: grid;\n  justify-items: center;\n  gap: var(--space-3);\n  text-align: center;\n  padding: var(--space-5) var(--space-4);\n  background:\n    radial-gradient(ellipse at 50% 0%, color-mix(in srgb, var(--pak-stripe-cyan) 18%, transparent), transparent 55%);\n  border: none;\n  border-radius: var(--radius);\n}\n.pak-hero-logo {\n  width: min(220px, 55vw);\n  height: auto;\n  display: block;\n  background: transparent;\n}\n.pak-swatches-stripes { margin-top: var(--space-2); }\n",
	"public/sw.js": "/**\n * Package-app service worker shipped via kody.app.assets (`public/sw.js`).\n * Precache the fingerprinted client from GET <assetBase>/__version.json — no hashes in source.\n * Brand revision: kody-racer list/PWA mark (v4) — image/* cache guard + handoff-aware precache.\n */\n'use strict'\n\nvar HTML_PATHS = ['/', '/about', '/notes']\n\nfunction appBase() {\n\ttry {\n\t\treturn new URL(self.registration.scope).pathname.replace(/\\/$/, '')\n\t} catch (e) {\n\t\treturn self.location.pathname\n\t\t\t.replace(/\\/_assets\\/sw\\.js$/, '')\n\t\t\t.replace(/\\/sw\\.js$/, '')\n\t}\n}\n\nfunction assetBase(base) {\n\treturn base + '/_assets'\n}\n\nfunction sameOrigin(url) {\n\treturn url.origin === self.location.origin\n}\n\nfunction isApi(url, base) {\n\treturn url.pathname.indexOf(base + '/api/') === 0\n}\n\nfunction isManifest(url) {\n\treturn /manifest\\.webmanifest$/.test(url.pathname)\n}\n\nfunction isStatic(url) {\n\treturn !isManifest(url) && /\\.(png|svg|ico|webp|jpg|jpeg)$/.test(url.pathname)\n}\n\nfunction isClientAsset(url) {\n\treturn /\\/_assets\\/client\\.[^/]+\\.js$/.test(url.pathname)\n}\n\nfunction isImagePath(url) {\n\treturn /\\.(png|svg|ico|webp|jpg|jpeg)$/.test(url.pathname)\n}\n\nfunction contentType(response) {\n\treturn String(response && response.headers && response.headers.get('content-type') || '')\n\t\t.toLowerCase()\n\t\t.split(';')[0]\n\t\t.trim()\n}\n\nfunction isImageContentType(response) {\n\treturn contentType(response).indexOf('image/') === 0\n}\n\nfunction isHandoffRequired(response) {\n\tif (!response) return false\n\tif (response.status === 403) return true\n\ttry {\n\t\treturn String(response.headers.get('x-kody-handoff') || '').toLowerCase() === 'required'\n\t} catch (e) {\n\t\treturn false\n\t}\n}\n\nfunction relativePath(url, base) {\n\tvar path = url.pathname\n\tif (path === base || path === base + '/') return '/'\n\tif (path.indexOf(base + '/') === 0) {\n\t\tvar rel = path.slice(base.length)\n\t\tif (rel.length > 1 && rel.charAt(rel.length - 1) === '/') rel = rel.slice(0, -1)\n\t\treturn rel || '/'\n\t}\n\treturn path\n}\n\nfunction isHtmlPage(url, base) {\n\treturn HTML_PATHS.indexOf(relativePath(url, base)) !== -1\n}\n\nfunction cacheableResponse(response) {\n\treturn Boolean(response && response.ok && response.type !== 'opaque' && response.type !== 'error')\n}\n\n/** Never cache 403 handoff HTML (or non-image bodies) as static image assets. */\nfunction cacheableStaticResponse(url, response) {\n\tif (!cacheableResponse(response) || isHandoffRequired(response)) return false\n\tif (isImagePath(url)) return isImageContentType(response)\n\treturn true\n}\n\nfunction offlineHtml() {\n\treturn new Response(\n\t\t'<!doctype html><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Offline</title><body style=\"font-family:system-ui;padding:24px\"><h1>Offline</h1><p>Re-open this app from Kody when you are back online.</p></body>',\n\t\t{ status: 503, headers: { 'content-type': 'text/html; charset=utf-8' } },\n\t)\n}\n\nfunction matchShell(cache, request) {\n\treturn cache.match(request).then(function (cached) {\n\t\tif (cached) return cached\n\t\tvar url = new URL(request.url)\n\t\tvar alt = url.pathname.endsWith('/')\n\t\t\t? url.origin + url.pathname.replace(/\\/$/, '') + url.search\n\t\t\t: url.origin + url.pathname + '/' + url.search\n\t\treturn cache.match(alt)\n\t})\n}\n\nfunction activeCacheName(base) {\n\treturn fetch(assetBase(base) + '/__version.json', {\n\t\tcache: 'no-store',\n\t\tcredentials: 'same-origin',\n\t})\n\t\t.then(function (res) {\n\t\t\tif (!res.ok) throw new Error('version ' + res.status)\n\t\t\treturn res.json()\n\t\t})\n\t\t.then(function (version) {\n\t\t\treturn {\n\t\t\t\tname: version.publishedCommit ? 'pak-' + version.publishedCommit : 'pak-fallback',\n\t\t\t\tversion: version,\n\t\t\t}\n\t\t})\n\t\t.catch(function () {\n\t\t\treturn { name: 'pak-fallback', version: null }\n\t\t})\n}\n\nfunction openActiveCache(base) {\n\treturn activeCacheName(base).then(function (info) {\n\t\treturn caches.open(info.name).then(function (cache) {\n\t\t\treturn { cache: cache, info: info }\n\t\t})\n\t})\n}\n\n/** Precache brand/icons only when the network returns a real image/* body. */\nfunction precacheImage(cache, url) {\n\treturn fetch(url, { credentials: 'same-origin', cache: 'no-store' }).then(function (response) {\n\t\tif (!cacheableStaticResponse(new URL(url, self.location.origin), response)) {\n\t\t\tconsole.warn('[pak-sw] skip precache (not image/* or handoff):', url, response && response.status)\n\t\t\treturn false\n\t\t}\n\t\treturn cache.put(url, response).then(function () {\n\t\t\treturn true\n\t\t})\n\t})\n}\n\nfunction precacheUrl(cache, url) {\n\tif (isImagePath(new URL(url, self.location.origin))) {\n\t\treturn precacheImage(cache, url)\n\t}\n\treturn cache.add(url).then(function () {\n\t\treturn true\n\t}).catch(function () {\n\t\treturn false\n\t})\n}\n\nself.addEventListener('install', function (event) {\n\tevent.waitUntil(\n\t\topenActiveCache(appBase()).then(function (opened) {\n\t\t\tvar base = appBase()\n\t\t\tvar version = opened.info.version\n\t\t\tvar urls = [\n\t\t\t\tbase + '/',\n\t\t\t\tbase + '/about',\n\t\t\t\tbase + '/notes',\n\t\t\t\tbase + '/manifest.webmanifest',\n\t\t\t\tbase + '/icons/icon-192.png?v=4',\n\t\t\t\tbase + '/icons/icon-512.png?v=4',\n\t\t\t\tassetBase(base) + '/sw.js',\n\t\t\t\tassetBase(base) + '/styles.css',\n\t\t\t]\n\t\t\tif (version && version.clientModuleUrl) urls.push(version.clientModuleUrl)\n\t\t\treturn Promise.all(\n\t\t\t\turls.map(function (url) {\n\t\t\t\t\treturn precacheUrl(opened.cache, url)\n\t\t\t\t}),\n\t\t\t).then(function () {\n\t\t\t\treturn self.skipWaiting()\n\t\t\t})\n\t\t}),\n\t)\n})\n\nself.addEventListener('message', function (event) {\n\tif (event.data === 'SKIP_WAITING') self.skipWaiting()\n})\n\nself.addEventListener('activate', function (event) {\n\tevent.waitUntil(\n\t\tactiveCacheName(appBase())\n\t\t\t.then(function (info) {\n\t\t\t\treturn caches.keys().then(function (keys) {\n\t\t\t\t\treturn Promise.all(\n\t\t\t\t\t\tkeys.map(function (key) {\n\t\t\t\t\t\t\tif (key === info.name) return null\n\t\t\t\t\t\t\tif (key.indexOf('pak-') === 0) return caches.delete(key)\n\t\t\t\t\t\t\tif (key.indexOf('package-app-kit-demo-') === 0) return caches.delete(key)\n\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t})\n\t\t\t.then(function () {\n\t\t\t\treturn self.clients.claim()\n\t\t\t}),\n\t)\n})\n\nself.addEventListener('fetch', function (event) {\n\tvar request = event.request\n\tif (request.method !== 'GET') return\n\tvar url = new URL(request.url)\n\tvar base = appBase()\n\tif (!sameOrigin(url) || isApi(url, base)) return\n\n\tif (isManifest(url)) {\n\t\tevent.respondWith(\n\t\t\tfetch(request, { credentials: 'same-origin', cache: 'no-store' })\n\t\t\t\t.then(function (response) {\n\t\t\t\t\tif (cacheableResponse(response) && !isHandoffRequired(response)) {\n\t\t\t\t\t\tvar copy = response.clone()\n\t\t\t\t\t\topenActiveCache(base).then(function (opened) {\n\t\t\t\t\t\t\topened.cache.put(request, copy)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\treturn response\n\t\t\t\t})\n\t\t\t\t.catch(function () {\n\t\t\t\t\treturn caches.match(request).then(function (cached) {\n\t\t\t\t\t\treturn cached && cached.ok ? cached : Response.error()\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t)\n\t\treturn\n\t}\n\n\tif (request.mode === 'navigate' || isHtmlPage(url, base)) {\n\t\tevent.respondWith(\n\t\t\topenActiveCache(base).then(function (opened) {\n\t\t\t\treturn matchShell(opened.cache, request).then(function (cached) {\n\t\t\t\t\tvar network = fetch(request, { credentials: 'same-origin' })\n\t\t\t\t\t\t.then(function (response) {\n\t\t\t\t\t\t\tif (cacheableResponse(response) && !isHandoffRequired(response)) {\n\t\t\t\t\t\t\t\topened.cache.put(request, response.clone())\n\t\t\t\t\t\t\t\tvar sibling = url.pathname.endsWith('/')\n\t\t\t\t\t\t\t\t\t? url.origin + url.pathname.replace(/\\/$/, '') + url.search\n\t\t\t\t\t\t\t\t\t: url.origin + url.pathname + '/' + url.search\n\t\t\t\t\t\t\t\topened.cache.put(sibling, response.clone())\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn response\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(function () {\n\t\t\t\t\t\t\treturn cached || offlineHtml()\n\t\t\t\t\t\t})\n\t\t\t\t\treturn cached || network\n\t\t\t\t})\n\t\t\t}),\n\t\t)\n\t\treturn\n\t}\n\n\tif (isStatic(url) || isClientAsset(url)) {\n\t\tevent.respondWith(\n\t\t\tfetch(request, { credentials: 'same-origin' })\n\t\t\t\t.then(function (response) {\n\t\t\t\t\tif (cacheableStaticResponse(url, response)) {\n\t\t\t\t\t\tvar copy = response.clone()\n\t\t\t\t\t\topenActiveCache(base).then(function (opened) {\n\t\t\t\t\t\t\topened.cache.put(request, copy)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\treturn response\n\t\t\t\t})\n\t\t\t\t.catch(function () {\n\t\t\t\t\treturn caches.match(request).then(function (cached) {\n\t\t\t\t\t\tif (!cached || !cached.ok) return Response.error()\n\t\t\t\t\t\tif (isImagePath(url) && !isImageContentType(cached)) return Response.error()\n\t\t\t\t\t\treturn cached\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t)\n\t\treturn\n\t}\n\n\tevent.respondWith(\n\t\tfetch(request, { credentials: 'same-origin' }).catch(function () {\n\t\t\treturn caches.match(request).then(function (cached) {\n\t\t\t\treturn cached || Response.error()\n\t\t\t})\n\t\t}),\n\t)\n})\n",
	"scripts/prepublish-check.mjs": "#!/usr/bin/env node\n// Pre-publish guard: run before `kody package publish` (npm run publish:kody\n// does this for you). Never prints credential values, only file names.\nimport { execFileSync, spawnSync } from 'node:child_process'\nimport fs from 'node:fs'\n\nconst problems = []\nconst warnings = []\nconst sh = (cmd, args) => execFileSync(cmd, args, { encoding: 'utf8' }).trim()\n\nconst pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))\nif (!pkg.name || pkg.name.includes('__')) problems.push('package.json#name is not set to @owner/name.')\nif (!pkg.kody?.id) problems.push('package.json#kody.id is missing.')\nif (!pkg.kody?.description) warnings.push('package.json#kody.description is empty.')\nfor (const file of ['README.md', 'AGENTS.md']) {\n\tif (!fs.existsSync(file)) problems.push(`${file} is missing.`)\n}\n\n// Credential-shaped strings in tracked files (values are never printed).\nconst credentialPatterns = [\n\t/kody_(at|bc)_[A-Za-z0-9]{8,}/,\n\t/gh[pousr]_[A-Za-z0-9]{20,}/,\n\t/github_pat_[A-Za-z0-9_]{20,}/,\n\t/sk_(live|test)_[A-Za-z0-9]{10,}/,\n\t/xox[abp]-[A-Za-z0-9-]{10,}/,\n\t/AKIA[0-9A-Z]{16}/,\n\t/-----BEGIN [A-Z ]*PRIVATE KEY-----/,\n]\nlet tracked = []\ntry {\n\ttracked = sh('git', ['ls-files']).split('\\n').filter(Boolean)\n} catch {\n\tproblems.push('Not a git repository. Clone with `git clone https://kody.codes/@owner/name.git`.')\n}\nfor (const file of tracked) {\n\tif (file === 'scripts/prepublish-check.mjs') continue\n\tlet text\n\ttry {\n\t\ttext = fs.readFileSync(file, 'utf8')\n\t} catch {\n\t\tcontinue\n\t}\n\tif (credentialPatterns.some((re) => re.test(text))) {\n\t\tproblems.push(`${file} contains something that looks like a credential. Remove it and use Kody credentials instead.`)\n\t}\n}\n\n// Publishing ships what is pushed, so the tree must be clean and pushed.\nif (tracked.length) {\n\tif (sh('git', ['status', '--porcelain'])) problems.push('Uncommitted changes. Commit them first.')\n\ttry {\n\t\tsh('git', ['fetch', '--quiet'])\n\t\tconst ahead = sh('git', ['rev-list', '--count', '@{u}..HEAD'])\n\t\tif (ahead !== '0') problems.push(`${ahead} local commit(s) not pushed. Run \\`git push\\` first.`)\n\t} catch {\n\t\tproblems.push('Could not compare with the remote. Check `git remote -v` and run `npm run setup` (or `kody auth setup-git`).')\n\t}\n}\n\nif (!process.argv.includes('--skip-dev-check')) {\n\tconst check = spawnSync('npx', ['--no-install', 'kody', 'dev', 'check', '--target', 'mock'], { stdio: 'inherit' })\n\tif (check.status !== 0) problems.push('`kody dev check` failed (see output above).')\n}\n\nfor (const w of warnings) console.warn(`warn: ${w}`)\nif (problems.length) {\n\tconsole.error('\\nNot ready to publish:')\n\tfor (const p of problems) console.error(`- ${p}`)\n\tprocess.exit(1)\n}\nconsole.log('\\nReady to publish.')\n",
	"scripts/publish.mjs": "#!/usr/bin/env node\n// Publish what is pushed: `kody package publish <ref>`.\n// <ref> is, in order: the first CLI argument, KODY_PACKAGE_ID, or package.json#name.\n// Some CLI versions only accept the package UUID; if publishing by name fails,\n// pass the id (search `@owner/name` in Kody or open the package page to find it):\n//   npm run publish:kody -- <package_id>\nimport { spawnSync } from 'node:child_process'\nimport fs from 'node:fs'\n\nconst pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))\nconst ref = process.argv[2] || process.env.KODY_PACKAGE_ID || pkg.name\nconst result = spawnSync('npx', ['--no-install', 'kody', 'package', 'publish', ref], {\n\tencoding: 'utf8',\n\tstdio: ['inherit', 'inherit', 'pipe'],\n})\nif (result.stderr) process.stderr.write(result.stderr)\nif (result.status !== 0 && /package_id/.test(result.stderr ?? '') && ref === pkg.name) {\n\tconsole.error(\n\t\t`\\nThis CLI version needs the package id. Re-run with: npm run publish:kody -- <package_id>\\n(or set KODY_PACKAGE_ID).`,\n\t)\n}\nprocess.exit(result.status ?? 1)\n",
	"src/icon-png-bytes.ts": "/**\n * TanStack starter default PWA mark bytes (square TanStack koala).\n * Worker `packageSave` embeds this module (text-only). Git lane also has\n * `icons/icon-192.png` + `icons/icon-512.png`. Both routes serve the compact\n * 192 mark so the embed stays small enough for publish typecheck.\n */\nfunction u8FromBase64(b64: string): Uint8Array {\n\tconst bin = atob(b64)\n\tconst out = new Uint8Array(bin.length)\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n\treturn out\n}\n\nconst MARK_BYTES = u8FromBase64('iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAAAYFBMVEX18O7l2tbYzcrNwsHEure9tbi5rq2wqKqmoqSmm5qelpiakZGWjpGUiYqPh4qMgoSAgIOEeXp6dXd5bWxsZWdiWVhkRTUzU1k0P0E3MjEnKCogGhoICAgAAQEAAAEAAACyYd7hAAA2k0lEQVR42s1diXrbNrekFu4kKHCRuSl+/7e8M3NAipKdJm3/+zVs49iKLeMAZ5mzIvrcnsfjgQ/8bF3XZdVfD75qz+cf+kTb6rXyx+c0TGPne9+N49ANw7IT96fSIAK4tHUeh2HoizRN4iSOkxSfJKkbpmmal88faziiP5AArmtdhrbAyuM4TfmHq49JAZ40yzxIm8BQfyIJPIFl6itsO9YLCvAhzbD/yZXrjxO9GCdZduunP/EUosc6dRn2WeuML5dLfInT5BIn5/gap9ervU6uSut+/vM4KRqalPudxNeLCDjjARlnfHI5x/j7er2CKlKTxFkDsX58/kkURBn5XKvHYvV/eIyU84kfLjGoiHVKzYBj+IOkIQKz28Zf9oVfScflSn66XE4nvYhTIDeB2MyDk/4cCqL4enmu/SLmgRiA91PsOrkHr1/PJ71+vZp+dcOfQ0HEJXKX9xWS6Sm2ac4P4H3u/iUQeI2vGSQm7eblTyFg4/azCKAyJZ/g4yUhc6U0DMkm3fwmCnySmjT/CQScsP3aWe7/hRufplle5Fms5eNrM8rGTOAlaVW8NPwZXBQF3hd3QxuRx2l8s5zGISvAMPwqw66Lk0gthIPmrpv/iBMAh1+5bCy5KLjaIqfOL/DgJddkeKUwYIEDupJc8BNNQ1JRHf33QozlADwUVd24piyx40VBY5zjlSKvuyLNnCuyOMaRXMlJZipMqzbTfy4IkaGEonaVa5qm9a4sXQkuKooS/JPXWHzlalCQZISn6SbOOAjK8vRfy0GEhYFZwD557TuPx5EMCHGW53zd1VlZlkWeJkWe42tw0nnDG+Cr5r9WRhH1DlaPP0XjfV3XJKDz2HQ+aV6VaVY7VxV5ydeqPN3Nwpmi0/3HBiGi1GYmvFhilRdlVTnf4iiquubWN1UCHgJrYfVgryKhfQ4ExHGWtfPnf0lCVPum8E2NlebQRGVeN00NArqm85CJrvNZQe6hHJQgscgSGYzN9MFRaOb/kouisi6c77oWy60qMJHD/ziJqqzAT1XTNi6DNctAAcQ6K+oqi6lHzwR7lGQQ10z/oTaNSjAONrwfunZofYcFVw0W7psK7ILF+ari2ZQN6CnztGloFK7n61WyQEFOsnr87yiIsF6P5cOj59ZjndSnMAptDyFI05wqqKqhjTrYCVDSlFBQiRAR4epZ6KMa/zMxiPrOd0Pfgd+bMsuKBmqIB9C2eNWVVKbk/JJmoWiolyAKRUl8RLsAyww7nhXt9OM/oiDCOn0t2FBUhD+1x+LxPz90fdNCg1YV9E9bUVE5fFdVNnVR01Jk2VWOJ6wePYT/56U+vj7SQo5IAQYKjNAUgBQNrRmWXnG/KcXgobIqmq4iNsph1uqGEgGMkaeQ49OZoK8e//9UEVcatgefWLxwj8dFru1cUTpXu7bvwUfYbJDQuqLiqVTOwTRDCqoK+hbmDqo0oWQ0dZYWEoSTkF1OZfr/dwjr+rnO6wJJBWP0Qz/AM7fQZ4TN7qFzmpuHMENyPTe3glkoQQ14p2o6HAWUaJXDGleF72/48XGapnEMHwc+/WCa6H9KhAVAlmXGL+gKYrQtypMAKIOS5TOCsI49OL6ucAJY2tAQEWHpUD8AP2CihrxUMzqHFc/zgmfGYxSMRoYomeZFRPyvaCDnfGLXb1DdUhlyFRVts8BnlvsBQgwdBCvge1gznEaP08ArFWMoSUGcjeMosmEN67a1T/p72R+8poMYp0DD/2T5n8vn7CF6jFwpdgXwFeuDop9g8TSNhp7rv/cwAx3haAskQQyEf07wMS8rIrnKFs91LzN3+vls+70ugYhpPojZv5FccE5PK0q8n9FLhw8C9ysGpJcPQDWYRVD42PehbxtIALZw6HrACoiBh+LBZwRxWdJg5fdhMiIYU1lX2/lwFLO45wEiRjuHH//uFEA/3ulGjySVD55cYln+Ex3Cs+I9chPTCOjZ91gzWQlMPvT3QULQNoSkMGo4pjztsfBBBEzzj891OfATXxs/8IwjqTAa7tPy+BckkHs6sA43Pt3ChooTnuSMXEIQC4cRQQNBPvt20NYxqUFKIAVtDRLAPTDAZToY+0wgA0e77ATY35NRICqWNaQaxn9BwjoNDdcPRtFub6G3UyBgfy6XCItsJQYj19/zFPgXmKdIeQYQiTxz4zJL1czD9FjeCeA/fDyfmSRwE0TC5ze6df/qx+fji7iAPae+5OaTeRgGOUQ9FeQ8W6jtInoigB+g6a7XvusQeAR4pSMT8bPW+X4KC576dTkQEBTQNI8fx2dcqP3IksHcPIHAKzB4tbb294+ppwbB8hMsUqFZRTzPioUodMh4IT5RiCSqK7B/SzEWBfeNkEHcdMdx+KYbjYBpGcb1oDqfNuGVgL4f19Xs26RvWaC29v2l/NuxjdLH/McfO03r7Ll2Rg8IUyyeGWcA71SfCTWQYldJRuf8egUWAgPRqJkZ4/b3wbTKRhFni4CJItAv61H5/5SAFt842fr1zzqNZQ0HFoy3fpd+rcm/uH8E80tqE5PURCGpQoYLrge93ySjZrGExjXqySaBAL4ZmWejRZaWdg7vbxpzOKxfO7vIMIzj2/rb223hGUxhxUML93QYgsnWzmwKbDQyzAiuY2WsYw/2Hqg3Z8oro2GCQ1jk+K9ixFBmOY6wv3QJeuP759MbA4GEwbUTl8ojeCNgDvv/QgCBCSjoP+eNAGxCSyMzSI1RMB4vQE2aV79z7LLLFgcX5MmqOqNPmyv2kFVlznhbBcWS4zVo2QjwDgRw/V5uPLG0nYKdATSrFyev8xsBc/jwRkAvAm5NtSwHAuBcyI6MA+2gScEo3bwwqQ4qZv6mFseRQbvQz2OMlksuEvp8DHIy6lzCOwG6T+FnMVAVRy3DQA3dGqpMgn1/OIMgE1jHYxy4US8nsFEiFhpf9t+5bDwQgKXxbfgGn09JeKqtmVRAf0Jj4Nf6FHihhH+FTS+aJofX7dsay3V0G7H0lMbVEF0EO+Za7rqWj8fU6oGCUYf+OfycgJcD2An4CCw0TXe8CEYFO/bklx0SvoqOzMYCGnAMQ0vozjXWsEhwWdsGHklZecegc8FYLWOikItIvNPBGig0RAe4rDcukloFE000r93PCZh3OzYYA7Wta6px4tGJ0aaOGztNUK/TE4C8KC9qLpk+GEGFGXqXppDXGr5tgdU1Ndgb7lbj4HHBqaJw55DlqKfw+qbkwQhwGwV+OwFaghnvCxOwLN8QEFbzIgA6g4rWeKJZxvEtgwhol23zN1g+7j/IB6I/rp+PuWvodw2+ggfrOriIRePARdhrxnscdqdkCBSuYhbhaPFybkpJBOQMrtghmCqaqDam4S4C5u/Wvy+k3ymAHqX8rOvgXcm/BnLQsi08uBTT8wAC71U3kLAO2kPWbsRwD72H5DLe2TTOkzgG4jwj6YWLtP91rsUzAZAYFwUSsHEiYFllld7U6DIHPH3YyP0EKAJguzTGm19qyCeA1LBI8U4HMXhbP4THtTPsWa24CEioMnzWVYx5Ut90Xa2wP1kJPFRFcMN8zQMA6kuYOxITkQRxoqRgNq8UiK7/loEOalQLud3wR7p99Rl80jw79+sYCNi5Tqsfg+i0m+zD/b5V448Vri3xGBzeoZGfzmAt9hWbLWeMiZgiTzNqIcDl5Cp34WoUSBuVTkcw0I+BGNImQLMv63ccNL9aAfDBrYfQdPMAvdFgVWm6APn0o+CH/ZmfyuvJd7C08GFBwWNpJYZg8Gmo6rpy9NPp38JVz8gkOeQZuxx5+u5JEkz39eUMvKSAQIgEMJQxjI9vJHiX4U0L3apM/uVCHdIyvJFPSyBgXg6O0KgfsZ+56QDoJhbuRtlpuzqHdR1HsFUDReoUH5QFgybF+zKEG3kYtcRS8vsRHCggAURh43hnzdAuBPOLTza+EdBk/XKHBI+tlAvlbNwIWI4UHDQvCagr+bnY6Y/PEb/d53XncQIlQ50K3fIwCn6ATQBjuRonkNr6wxFAkFOqIpIgAu5wstZFoIs8NK8v3uT8CkaHwEIVJKCf1w3g8iEBJgMb7aPJzvDkIDsAUFC1j1F6BAc4j3VDddIw+1VVMLsQq5pxn8ZXPqICuj5LCfYTeBLASMlEVNQFPRSWfTyDoz7HQoqPCfrrQQTR9cHBWAiJ1hcCvqjecAD4/7ZOXk/dz6NCPozZKsgMEsCUNaPkkIYIVu26+Wy7EJg1kBBQe64r1zO07bhIk+zP8oWJjICByGyduh56ow2QZF742ov0jLsNfj0AkHBbRy4fKxhnBc/5RVNzySVj575m6LOoywiu7/V8qFYxAhIJQcMo731agbJGBh2LDr+YjBQiKQd2nnZ26Hut388LhaGG/BkFOL2xe/Uk5ukoBDiAjYAsENAMVIBjx8AJiBCKywqpeJpkECAWul5+QgBB0XAH12PfgQaBAKeVrDsfLdnzOIJZ7T8Au3FYfeFcSd0XlBmgVDetbypgfALw2y2sn2q+pRaqR/2CsW0IN7Hqilm8VEnhDJia6Jpa6CADmxBsR0AZIK7vPJ2gPK/oFkxSrJs38CLMlIVJIaRl0FoKbBuwfC+XEvuwvumhcUewtycHZQxl9m3Tm9M0ts5xx5kAKwooU9gBX2ae2TAXAfAloWTofHkasyTTCXTUolPv9dZ5AXYABZOCo++2bMM4NAD046wEMq0q/LqaBIDqsXuNiY2miMIRBCVKAtJpwWoXA9/YPiKHqmYKvqI1dlXt4WC6yvvIlXD4FWHZHNELFalMgWkhANuCpiIBAMHXc9j3HZAFv9LAgYUi5j6LzadNshS4voAKkez001ELm3+8W2K3yQAQ/wrGH+aWAVd6OSUZK6f0NsxeQwXVvgJsKxtpoXcCroGHakNDLV3o+BpnkmqGEFZzJW0Bw2gf+Z1dU2AdbZ8zGMgoFLaCqB3otwMBQNXrN1HJzZRtFBRZv0Jol8FPPO8Ry2aKN3fAct6DcXAEEGvH6jcJMdYfPSl44gkjoKuVzmM2ElY8KWDBpRansbUoZFPpkBSLYg6WFZoshLQ4WsIEHKP0jI7RpXkzgtO4m4JbbRQU6Ti3fpg7HhhZqG3ozDPiDJ6Gs0T9it/MWqAIUCjRCZyeMrAZM8rAOLiMi7pQg5WJwsRg7jyrXCmfrkiyOMXvBYmsKEoZAE9YonnZKkJqJ7WRA4osYz9N39jxjQLJcZFVy9B6HFdP6AUCOvAJBLABJOoYp+osyAHVnkd1wRjYgYBdEZEAWIEBaJgcrXBYGlstUZFmWn1Kii4wJHGukiIG76/km4yBJwY2rwwpEPimaTUsD6xmWr4lwEggBUX6gQOAHh4GI6DvAINK2OO2HbpGi1cst4c3FpWUT4v7nl9JyGmJgYlZBBVbUVqsgsaUKb7cuFt8o7RPmmr1JIkVX1ks+8KAoOIf1O41kBRsuhnBpw2ELg1hNYZj6LQvfdspNjkxDjk4Yh9Xs5AAxt1Qfn+/3xlMivDGV0nB6Vk6uhFAC9SyDi3WurGTPK8ky1zpC1bOpRa8SYlRMjH+lfk3pk8q/JtRkMJ1YkyED+wzKJhCUGk5xid6pVlojmPY3nZaFBGhIh0IHRp/a/e4lYXy7tSQEesFLtdgCHZrJrem5vqbXEkS5oKrhoVPWcnoXttWjDoBmKfwlnyRyElKLR7FcCbLdxQ/JsdBg+dVSRIzCj918SsBFOVB2cZbk1F6h3V4EtB0wbfxhjCVUgogN7IC48sxZ3DV3gJn4Ezp+CvRgAUVLD0D++QsrYPKKx11W5q1fcMoVGL9BzyWmAGRokovkviU2fOyaEqqsgzaZZKT8YJmg0kABVU/Y3/FQPcRBEALYecZGAMBNdDoFvUxiBWlcsaeB3AOpkw6FE4EFhiroyBn6Q2cikZOEQBJRixC9yhlGUVDqJFJDpRTzMqsTi2pm4kZ4cJCKkBD2VsaQmHF6Y0CJkiw/mVueuJIIwA7ZMET+pM8BPrrhrCGF3/gKQRJUnrlOCotS8q5rRWFxLKrkqsEFTArDaPHOXP9MGAdi3EIQ6CTGFLOZRoyYGqwByOZF4oHftIrHTQoSD2G5FV4Rqx/XrFG14qFZkL53kKHhKMBZjaCysM9ooKjS2w5qF0G0pIHANhdMghZVM6BOwFCaux2Dk89i2Ebc7zgCRFp4cD1gIuFZ3YILM9zoA2m8DvGnccagCSm0WCBXtmMYmPF1U0XKco6MEA3rx0tB2zVTgADz3XxDL3VASfco8oysMfEGWUYzijphg8neXYMi3X+xtiMV1lj6RhYov9a0EvKa5bmFDVYjPF7V5DvipwqOOVKQICXKi1ZkeplnaseVr5rQyhcubkWFhYu7FBXVAOOtIAAfo9X5WRAysHjJRMBTmehDnHD1BJhEkDgQZ8/Z6UB3fOuUWVmCyyLBROQ5DEZBOAEdqZ1oMY1tWcQtlDfDdUUDoC8AIuOY8xzR5GuBbWZJmo6R2/RFKOEA0BrkESljEwPRKP8zdgghd4MJchlpCQMEQ4mT6+vWogwz5M9WvqeJVGUdLRvbgqT06J4FUMBQOSq4cKjCD6XC9cNPKSkCuUHEAz6ZFAdSVWWcQJcRO2VUDWEFiPgOA8MLpw7dQ0DSTT1aUm7DQIUuzUCrhvWBBuBgqjCqefJofEEp0EbZg5pQ/YQhqKd9N4KQlji1UF1quipYIhPhQvKocDNo6+XMwCYFGUKbWC5Q+bPiWmwfwUAQ8FVZlYxwP/j2LlebFsx+KOi/2vCWjBCifq5+mugIacY1F0ETVPn10MmlidAFQSlyaAdK5K5cK8Aj51zZ/UJXjFiz1oicHZZeOZPyEweXAdh0RFhEwcVtfAI7OBEa4FzymRkyO1c8eUs3rWIoYw/7XrhWX/CQhsVSlyuG+AnExVNF3mG1pODLeMBSGlhJ1hFxAClV3iCEIrlLN6BmyQikGNX5XYOrqwLJUuyrKbR813Jcvis8EqRTD01CdQay7SLpmWYLTdFlVaGxa86fLxZmRpvKS9cdHqj5PqMXj0pgFMP2Su30NwmAhRgGj3sPvxpbr1wtSWcwNEsovCdfdkUENSKG8taL5qXsmCRcl63ZQU+yijCVPgD4/1knBInBqWi7ONFeKps5KFDw15VR1OkKkUhMqcjiEPNjYDrlvKmpQL5RoC3f7cz0L80qliBF9rcGi205wEERiALuZqBDqJaMhrYoWQ40JWOmbYKegkLAvKtGJNgqdR9kqfflhngB+v6YVAVI0msu4IsBXaCw64TSU3PAMXzEOJQ7bSv/7z7vQDMEXU44bFVsxiKIwdVjCo7dzPPwYK8o9HCeFljCQhykWfU2LHUFFoc0g/nDMQX7ThUgh5MHdFMgdoqL3pWQlJBFVxrLqyVJRmLUWHBIW9lJgQCAQZkZ2uMeXhhhy8HuMOlNlSjOHqohUS9brQAXL+rYLxuzik92htuGqXOaQ30NCr3BQWsDGTmB6Q4aKfwZdn3ZBWGZQFqZqZ3oNQq/hxdJBo9mpW6YnsL8QXNnIOVLEKJUCocbz7Ftv53wFM0UcvYGcxsTcGndm0sZ0Ptc5Pe3IsP5L/z3/gd6jWQdNsfWWiKdlPW8KBgOKGQqo6ItgsxlYGK2NEAuYYV/44qrA5dtIyYgxvoWIC9JAQiQMUp8Ste2yigNYh4zIwawXHFQZaEQNipCuu/UY1a1cGW9AYeKICmadzq0nKajShm9q2jAICNymbw0NC5QGhr8kO8w+Au7S6jzNowaF8cH6Q5sYABi7YheOAbyYGq43QGF6s039Da0eKWEc0tewU8bZbiuSzlZfgRAoAVDeb7WL54qNMNj+hM4f5Sl9W2+11d1WoD6XphPMYUoYIkPyoXYYSjVV0hg4XAH4AdlPPGC7jCqgDPukwdRimh+ZWmzjojnxVPLxSkEXFS2xlTy+vHmQO1sA3CeTmhAWhRE0qojP9C36sBJ+FDQlw4rngvmTqfN1ai2ncW7AIBI71DlkjicIUtAPdYxAyRqVg1XKikmS4EFSzbk9gAFj/Z/3SkQCGPiCl0GqfGSo5l8nGSZck+DuIsv/HPPFxf6722XUhiy4gQN+VEKHL/moI1a4BBKjwareAx6LFW/0IjCbOAn8B+Qeor6CTFOdiJkZ65WTFBRrJRcLJnYyTxEBPdtPDaelnajodaqA2lUzVgt5VyHd9ke6tNH5gbZvYxV5a29jrELqgAatJZSQ6cELaL5pF8CpMNq0JUlToYROvmoZuXyg4TtF6tBe/05VfTZkXc5BvBTShzYvZYKN95AxAmxjAC8QmUn768z+VZ23PRexpYLCpwYqXsgL2BecHyTpRVBahl+UBdMVbo6KdmUCEA2spcwBxntMM4DOu3PX3zmyHDRcTsU9t7I0DFNU5lCBRjxpBCHdQ4ly+rfx7G3kN9CY3gemCHqNqYHei3LVDpymi/RmfdmwKGSoImgW5T9i5TAphQPN70UPzNCWAFVwYPI6y+ay1SF06gkn9IZOnFQKo0Gdovq98pOD9ffbLY+Vp1DbB1qzK8cAb0TswchspCSJ/jKcOAE2F79ljgJAr60qrZvVq18VkFdO8nwIA54fTtRrzVmQDfnJrgyISWqw++0uX5k9t2RHwUFw6fnZ4vR6dLHqrcyfxWlcwv7qMRJO9AFsQrgNy3ReZ7eHQERTSqKfBhmRBIMBxIi/y2hTzmAifQNEzoG1wbewXxmHwq6JnovUnAVL6uHys8M6AXvT4HAqLzsJjWMQHm51scnsAagA6/y5sNZ/xgsLxvW8P+ASbFdOur3Jo3LziDr+u3E2jNS+yZyYAJluARk6dCVq0d9NDvPy52jC71/a708ZB+ocA+y6dVOqe3EBSDQJZN4Habfyh9tZVlQLdSm9N5gR7PFJQnpghOwPWL+jMtChaiaQkCPLQqR7BGSuh1WQGJ8PVl/89dSJKxeG/K34+AXNUpxGOVhF5BYoaAmC5naYk5VlQyqh5jp5RsuL65YgArTZ15a7GV/32R4nAAAHPeUtF37o15gQy6qWa6pd9CKFYftf4pm5bpWS+xLvfLOwXX7q5Q20YAC8xlywAG1Yy5hcI1JyFheI8tI7VFA1V1nmaqS1Fu4qpKzLN25qiFMjr1vn2WHDeZubFKqBDPNU5+S38+Wq1yWe5gIPyZ8IHK8Z6+EHC+dsNk9a0WNBebq2pk6hRAOB2i+eai07eB+JbMYDDM7h0LLhOW/alnwDIA0SsBue87QIl2L+5r1JmbqqHpxuIcbw5ZvNss/Cm0fnuSKKpFTH4g4HzxwzTtBbo91bTCgMRDfZKcX/j4kJWDESeep9amR1JWW98Gv+GLJRYHeUYl2g3seKvrpaPDcgpiPMcyi9v+IyCgmMLisUYpoQafz/d4IwCrKk177hXGqlex2tx5aaTD3qCIjiG2qjcreaOiqvLg2yj+f343www3dz5S1a7irEOhVAuRGRQQ4ymtq6ml4800QW0mYfkLq9C05EvXgYIh2AKwatpbX9D4LJHe8AgoGC9HTjgfjiGAXEpFLkDrS8fArazA5R2FXeCAgj3AQu1WUNKk9mQMfMA3crX02+DDj16Sc3Tqw/L5GAHXew2WnwpjIKyBidh3AgKT8h/q6BQdefn0ivSNiLQRBXKQN2f+jQD6vkBBEQO25jcW1nTAMl9HIXbYf6ChIQs/c03PUSwGYraaVfRioe4ODHa/D1oVTqke9jazbwiArXy1219Bpp7MYLdjjeve/PsCRZOyAR5sI5Zz3RTwzJRbSdhjr6LSwvJqfQAR5wQEtBLgTYFOoKCsARooyZdIYlJvAIKLvX9DwZBFf0XBBq26wfITucFp6qFXCU7py/o2Avpg7Lxvmc6mJ0csZVgosw7p7Wey7HKS9twL/9ZlnDtaXbw2ZSIgV1XEXmV//4aA7hR9R8LxNXLmoNhmqW4Ndf0eTwCeTq10MQiobnJmmswmUeVM3uUQAUa36WfG5yDC9O6AB+739VC5uN7wyzzPxEdkYB8YKBAw7m0OOwsNQ/IVOL3CEf3VANjUTW9q6KppQUF/nc0VIP7wdQRev4EApzazTAE/Ork5o7WOPmWQnrNsDdd8twL0Lc0YRxmN2TIQZqSma54UbA05Wz8CXr43mxhHf/WcIaGu0NS12KYbXc8HBmKCogX2jBRAaVsRwMZgBSXTItdkFezAk4AkucQ0YtNDDQD7Y31N60ic1z3XvxHARFXbbg4TX+6CIo1OCYXnPrTpd7SU8BW2To1YPn5YyTlm37bKq3sfVXVV3TxTQhoPQxEuSwZaE1h28tBGNkzE+SIrzG63deej9fMHodE6nK6XbNwYaNr1UK++OiudE3H3/mzsk98FqdjNuPSXr6zUt8y+sHPS2hAvOzOXCqp1RgBM9s3Bc7ioOYiduIWyCSmBSdl6K2U5Cf6fpmDERMCqTx6PHwtkYPWn+NJP4+v6jQD2uUtX3/mvQ3elqkmkzKYN1y79u3MRpTAErMeySDuO4KzhOnB1fGsBQYhx5BnCLQtNi1CCnbFWxXryovNV78+BYS8AVVEww/YrQzfo58pX1hQ+6t5a9UYA7KJVQIqAHpr35A6QirAQSjn9cgSNym8s3Xy9xubbpIyeQ2vWqiqNRuYoGNm1CjGGiRmWZL6o9D3s2UbAGd8DfXO0A/bgAOiuw4b5ZSusf7IQwza1ouytCcgIz+l8btbn+q9nrzcd8zcKfFvSJJWZHYFqIjgLphL3s6Kz66N16irWAAj40RhnbBJydQlkyHEAxXUnADxklvj+UoG/cgPXHmpqWPZCtP0E+i5UiRmiJgHTdC6XDRPeOwms0OH4dgYxq43FEoQSVxVhlMr+kCGdPKUILlXJlhQVOahuoHQMLDJLosB9etlsB5jxNByYKLRTigGW5JrXLA0dvxBAEai2CkJrAs/nwDl4xRRQxxj8NLxqo1PrFGIIxVMpT5I5x6qoGU/3tHXRAwSwciCUm6Qp9So0U8WjyAHJk83/gPFIr+EI7ksoczBOxgEAT7SMHb4TMChWL8+37YMlGOcAqVY4mbZWYMbhPk+31yNgmkoUcPzDJS5YeFlVCiYzBw8z1USPdaiKREVk1uUNbHFjdJHxWhgzliFs5gPfccmnzR1QW0GgZrrCtCsMMW7rfyWgqAIB4rElrJ+CZPpzmqBSAEheVVHVFS4XARcGUSqnlCU40jPVwCC3EZArW0sJLppGAlDkzjNDZ5UU122mE21BfdQe2/pTqDYSMG1NhkcCWF8hArpAwKy3MENioLy+4whyf5/cqyIdmrJOs5DdiOUAtAy9pY36I0CDCKjSGIYrVZ6UNRWUfNBBe6bGe6vMPAvUXs/l6/IZN0+BbTuIwDx+QwCUaGnR4j6YaRGwG8IhvkCZROeOZnl4gxPqmEnikGpq1DtPA51VqntyPvpc+4pmOGXNT8kaX9YQ1CpzU6TYaplDhuSq+sVXBb7010QVpdaUOM0vBNCcUgQqmf4xFPfi2D6PkDA2TIvnRYxP3uMAckZJyUPKODCJUZbgFd9CzZfR59KCAMIgtmdVOO6SdRw1pzFw4pOg0ZaRCS0Sl3LY1z8N+SWDwPhmsODbdKzKvSsRSwfP2kGOBGwUUBX0gYBlOb8SoJoLJWtgJ7OiodzCT6lqHmnTFAkJABAiCGJBrFNuW1MC8IWvLUycybcOyXAalDOcGK+a1+zCytY8K7wfF1VNT4dKvsEMcbNhrwMBnPl5OIL2dDbd9oqJas/ko5XRwgvOmZVi9A1b7EqH88nIQqWmTWT1je4L6SL84rYxaZuVzONl5qxZ0nCrS7DURi45z72fvhBwHwyM8t1CP864KaH188BD6/JggGxjpoMUs5mPSRtGtgDUWrZTcphNQ8503uU4AW9ViQWTwsDVbPGDn0yUynrNwnUKlqoUMU7jvaTiagNCSJMyzp516/evJyAKmm39gQCC2JeGLkDS7wgoOwWrLcsR58xJsamiyGiGFQaNHrOHIeZ0oYqFmQwEcb9wSszEMfkEMvNSEx4yqwU9hHQUTGCige1S83H92wmYc+4tAGgEQG1N9+m1IY3o9qsMRFnn2PGQWMY4aeDjOEZLas58KajdeAIFEAM2u6idu0lKlKMpwnfSab4Vza1kzkMVPlul8hbhU3EFo//jME4vhZTBoey63aERAet0wFPBs1hXmZTp1RRf2LVhM1WIQ0tWnpV01bF6rNI10EIz2IzVtdjzm7/BvQTLUwexXgBywL0FnGxYv8gKK6vitdStpcYIb62cnVUdr5WgTwq6PrS4k4ADnlrDlIOASdpXAq7MI2eJhecuqR86V6g0oqo03q+pcAIdWIjcAWWI9VvKr20bFg2wAxNOARxkKNciA8DTxEjWNahOmvWwpbPwvAh4HsELAZZk2EVgfCLCsH5QZS+9OWZxqcJxOcWXOK46lpZwczUxhcUJ0EIwC1ls7UHMigFkq1+lZ4WoZwIRXMjGahgPJ3CRZAocwdBZaUsIOqg+8mkGRMB9n73wXP+09LnBEQ2cCNMF7IU+esWjSWHDTmEGQELBtuIKbFGDjoQFtWVBNQo7wBJzx3orZjtYXNmyeI1TtxQkYr5GEyWJAVm2q7Qoq4SMVq2OKdT+aYgPsblnVEUvL/7ygsqXwD/Ecq+hCnj9aabBduLXXMM4OXgKeA7gh15X9HgMlSIpkFbmpRkKtyFtrAPCi60vqHprj7OEr8DqXBoS1nywGsibt8s2q0nzAw4tz08CDuuH0wm1/kQi/Pbti/ztAE6XssqNAJrQ1LHUqvNcSJapECqNPh+DY5U2W2QKxyCR7ReDw8BCLF9kHSgn9RQy1oX6GKtcEJNT0Hy39Rq+sdBbfHc7gHt3jpP4/vVZ/Nv6o1PMGU1JGOp9Bphre+jJwmqCFa6gQ9NUTFqxZIN5Gd+rO6KzEiaWXrIdnClEZp44809N7RyMyZFhexn5+EWIn1JgCigcwHCN4D9f+i8ENF8CK+dUYRX1iJCGjJPYnAa6avIQ5ws9YAiqzIq1WHvYiKMVSqham89Tq5CF1Zz0kUvWYTaONUVVTbeaf9iM8ErAmxgchol1CsNfz8306lmkXyNDF2WMFZ2WQ+Dwq1QuyHExqiNPQcDaQSBYmZNqwlxvY0paV6mCRdM7KTfUpeKdnBbQaYBera5vb/X0HHvxYon3CK+FSbeIUWOJhOSSwPbty6+/BudOZ6v3I/eYEOStU1Uyhz2q1w2WGAQMHArMHocSlrgzAjQ1gKXeNSw02w4yJX/ASWVO1FrDCLII3bUbB5GAcZzmFwJCiPe5fnwspWkUZ0pqOGn3risue77/6M6oElYgIkB5ufUZibomqp6tYMg+H6Nz2VW12txZpb1t8AEJkIkuFCsy9gEB1c1II41+n600LntI5UDBRsP2xQQHzGKj26x3fRWSkK8HwKq4fQY8+SiTfxXG3bBUsqqhhSDFTmVrfO3mWdttwyIZGZUyZfCuKVhvqw4CkElc7qqG059AaUhzbgPoXuT49QEhbZZeQsrUFrllmN4z/ir205UgW8XTNdP0jtTKTJkFdg0JmIFQNbstY8Fwr/IFsZDqjpk5xzdyLDUfMA+sCB0ieumc6eC2GSD7kuefUgCRZi0MdvSZ0fhm80WYSs0SVcEHEM8BucI8tMfcVGwdCViGW5WxqyrTKAa2atzYQMpaiUYtzQQUtOGODMXQGPBUqXooqo7rBjRnUzrj+M0ZbPCoIqilZJ5/lh5QQjG0M16t/lUUXFRCl6mczKmgj70SlAG4BK7Q+GzOXtF8OcFVRrBpp0L8lyP/VFhFKF61NlRV4OvsbfAiCdBAm28oCC7CkMKqexca/WznT2+sc7Ux/LFqnVLr7lJUgvVbHOMNDdINmkz04C0oDwUmAPmkJWmLoUpJSE8OkdInAxVQmgW8N04FUesB9c+YntVHmusEWJc4CeB/YaNwAFPDxBX7SxltlnI/qXwkTJDTMg09M1vBkrN084kp0yyX1aAcpiU0VyzS2LfpBp+AiomTpZ3rhqbU8DOKgYRZk2RgGTomYEEPG0rxj8PchFbeZOQMw3nYmr2nI6zeUcQ4sDK3YFBB/b25SrKkZjQvCDSFXLvdrGERf2uGUFNgfhuYUbBhjjbx1UanjewCSNRWAU7pWtU1sxLHm1CoELyRagplrlaHAFxgVQBntuhpraFJ7zAGbArd6hAPKj41rAjkEx7m6jC1qhU4rFLncl4zuzAgDiU3xKOgeHi8TayLwmRGz/nSUqQQg5tyWqwnbW8awetZhu626tiKCI/c1fbjqMCjUv/dZGI6hyMIfD/aSD2tn1oYaJDOXU1Ak6qhy27M4EZn4TIien5FItbnGdCdpMmA/rTbPw4j9qIwGXP0IkDTuEK3pZB1aNVQaZiEG5atsc5AyvAw16nlrpg6H1TbGkpcA+OPWypQkXZgWnhTDJSV8G6hvKnZr2pAFmpW20C89QaR90kaCbiKg4rp8Q0BP3481qVnwFFFO2VoVWzlinMAWis3odJsfFdbQ7VNVx2mIU2Dkw/pszkQCq/sBKwhJX4fmVyCGwsJZsFyTmgIt6oodI1S3tj2xwziq2Eu5sfEKv5kiPEz1fw+8zAQACm4MeirxhQGsa3aUgQQVhBT1FZjT/uhqexhul251fIog9CPW1RxxxHiKvoKhTm8Te+L2NIR5pWoP4PJPLUxsvKeBQ/WwJnZJSxWbQ8n0G4LeHy+nQAoeAyaSk7I6aQiW9XzqglZE9wMmhJAaLBGgEDj1O2VJNQTuZTcvKO4MOUA3/XU9mzAZM8GhYDpuJI7l3OcHGWDA6ShT1Irok6tI1aQGpvbf7nuINrGZgKUcjI59DwDXF7p6Y4ljernYOUNIQWIVJuJGsoCBLpadXyIddHjL63kLExpoPAmz+WfIscpGzHDCrlaE0vGSrRyjsLLTUfZ7THUp7rXhzkyYM1h/cmNcDyV+VZVZa5qm0blvCzmYoCFC4djKSzEUsbaujeUNsX25uetjTHQYCfhNZKCOcr49Gpph94pYp6lltTKuS1l27LMge3WrLekGlUnjQUvpUTTwo1mv74j4POxDv7GFudMd0FIe/Yse+0E8KwNlL4NYYhvnxMl/XaB1vm1tn+rLnmBOefrGQi3VsA2t06TQlcDQEWUjPyxL5CeU5FYPMuq14kuQJcusvoZAT94sdqNmEI1i9xw2+VBVfhq6GP3rRmxLnSm0H9+rvhYPP0tTgNWKAZGZvDEzADrvphCSZwccCtPcrb3st4h0xU+1KyhIw4g8JtrAg4EQAym9uYy3Z9D75gNHG3wbrR8AFPrv7Ly/y4Uwl33WrEDBe8Ac6uvSYbeZ7mzPl1rSiwL+Xjcelg3KAk1u1KQBYdUbXMm2Pff3JbxJAAU/FhH9nBTQzAdSzQto2bOTaNj8UShpVrltj7gbGObJEz6OX3jnhjujy4gvOHdJLQ6atDOOTDIsXS3shha4TRUVAMGQpmB3ZWTDcvjrwjgGXxOHc4gVV1zwTQBoxQddZEG66k5iE211FB7PKXeGT/OtzEhbyVYz0qyzgGLcO12sQEZtmTrDJ2UmteA4Euge6oT3X8mYMqbrdLsxsqSvyLAdFFbl9ZEn/NXOYZuhevYYUUsVomRWqvWFgFN0EFWSQUSJMzv/QaUgJMbAchbdtzapDt1i1SO2UTCJLZ7Q8lW8FhrtnFc41A2Sh8+69bPXxKA74Ak4wyZ8oYo31hQx4n5bMAllLGGdA2naENIbvSHFh32VWxdL/uUistWMHLtOaqobV2peUtOQ1fA+zVYho4fVs3OcOwRK68Ka1K12a8A4d9e4xZ9HboNYFdxLCkj5xAozpj0Gngqk9zKT27U+RSCou3l8tq8Yd1Z2/zGzQbjdfYa8baDmv0mHPRR5VWpinkOm8G55GnBM268gie5tZ/It6cVfvwOAbTJY6e5pJoPa/Kqemb1FTNax9mZIsZOoHvToNGhAPQpzvis8Njfmp0ybPsuoS8r6H2snvPpNUaR1rhijYY6K+XK0NmhgW6mx+8RIHTdO3U6Wz8WEEXbWbeWmQGvOotNit8JeCrOF2V0OsXwk9gAz5EgZQV9wB4H6KMK5MjO06uK05JTmTVOxDIDqoPIsy+ewE8JMH3qmxtzOExI3W5euMKsgTIgN+W9QkV0/8ZC38cawEAsNeHiOH1ZM92ZitPIFnqpHEtL1uep5Hbzlk2XTuTsTp/f3nD7HQGAp5+fC6wytY61JDZy0rzZYsoBx64ojDoKj55fe+S+i5Wck46j6Akjcg6W4Mg+ksELe7LGpo0wHZTRq1UzOxuibW52VrbTT27DjH46/59mmQOt6MQ7zmPQbQTWCk105Bpl7r4l4PRdrKdg3VZRcmJArqoflTOwUp4JE07bp7dNfqrY30xgrBsGWLLhf3pzW/TzKwB+TFA0t0ZjbLRDQqUaskdhdvJ3Dmr0r06AUu1r9i2BBnp9pMGpoqyks8cBcC6oJAaneP1HrhIz9TY240+vuYn+6hYDzlBm9RNLKBgwMkRKAtQSvQmxf8dx3xEQpS0J4HQcTirirlSEiMrYQXyZRSwy1mcRVDAJmYYqxIL88/gHBISbDMCZtxs71FnJZe4kkUVvnMR0jj//EolCtTKyRG6kGWbLoUU3Tf2of9yraIQr1rVV6j4h+5R+/vn6f0EARWHmnMibQsEUCOkkZZ6UDdENKl+xtKHPIwFXijA3W5V7+KAAjQjgIGPP2d5FaVMx8kpTgGOgVUhJO/3VBdvRr6+yYbubSYOSArcbfmllo1dsMk3/bC8+EPByEqcoZ7yVxooh/hqs5DrFjXUbl1eJTMWJupWm5KSaZEOF5Pvp8W8I2Lw10ACddJNDxt9jYq3O53ZITl8J2MjYZFhQkNOLORWqtHFfVW0lSkS6jUrraPpzTppLCOoBUYdlu6r9XxCgH15X3iLA4KLjvUZVvRMAlfpTAvb484VKuNT0TvA4ZyGztIdZ69oUs+MYAc0qIoooNGXG8QbYX1yGHP32vSq8BX5i/pLTiS3XYeXXbTeUp/PPSRABmZp+OV+MLjA2n66SJR2U+KHNLDLdjiBfTcn0flp+eY9w9Pt3wzwkDxyIOrShRdJbbUU35KeXTvuvZ6A4DadmQMGUmgFCgy5MV8jCsJZELqa6QHnC/bg8fn2B7e8ToKupVg0iUwW0VWJRlRJVu+2ukm9piM69BmSwjCTn0EyLcoCngP0lT1Vp5ZGMq9VMQA/j+nj8jwnA+rn4D8Wfmcxv4Wxud3a01/PrwLoXApTRbzX4VNNJuOW1JgA5xvrKSn5wmXFGFztHdaPC/5aAh63/Y7Qri3i5mbUYdls1TZMcWvIuz+Y2+GNOQ0mo7aF1WM7D2xo0xVV+K2+laGTVbp7TaCZFfx6/dQFy9DfXLwbiKG3dV3QcjM0AUhXuXtkH6ehJeOMi4wMafLW5FcDQNS/AlHVkzyMtpNot/9YVSNE/Wf/Iy68U8X9rdLtzhpDNiNxnHWSdrjixwb+dddEzTM9hDAEkek2Msvza371VLvpt9l8kvkaAxrGruOO4dmufZ0RXN4Kp5H6fbCVnwovpWsu7eeUfrB+el12sj89/cCde9Jv6Z5nD5j+PAEz01ixpbf/3wWo+WJxv1U4qf9E4Uas/w9/9XoQT1ML6K4v1jwngm67jx7ixjz3zvAokvTYa3veUgOrsx+EoJlt3cTipfYD8vIzj+o+vwYt+Z/nY/fljfCFAlyctYpcXRuL+37cCofvrER2+mRfDLOEKhXFZPsbHP726LPrV6n+AU3DE81OAh+7JRNM4vG0wP7nvX4Xq4/0YLBLAayUfh+tr8O7zP73GL/oFhBPrk5+3E5iHvDcCJMhPAp60hCHUxj0c7KuaomAsQksfLOHh5o5R17D9Iy6K/op1FmN8HLWYVXzR12VQRJb9ve8Vfc+zmO57I73F85Ss7Rne85V6+J+XXuy6Qbfh/f1TiH4KPtcn14wiYMiHefR55/fXwrQOXRf0pOC+9bDK32nNfdMAHe/ajm3AEIGPLw9pkA378W8J4C48lletg/X3eQ73l2M8X0R5r/F+06lMlHeqAuatfzYEzYA/scL4lQA7lOXvWoLoZ2rHlv+xE/ABu4RDKOv6qItGpeC/CDKr7nqOPSNw5s47xuTpuugWydEGrn9zBGTY9W+R8CU/AMx/5J3t6foy9yOHaHUHungE1ET3cAg2hVZWqwt3VFkYiVMS5IjqAnMtFX5RuG/hCzP9HRKib3nnZY2merD27o6PHkft6/D6B49AFw4cjJTUDkOP9F/oerLtlLN2iPx1A4DuTPjQxXcf/a39+JaE36Qh+kZwxy/7P5N3uuEOoRw/OpzCfDDJc+g14TXlWr+NGA9x+LL2RUnnK8+qXPeuD0GCb237wVO43fpvSPhdTooOV+mu77rtQADgLpevW5bL/GP+KgZb2rLVMEYKsCCbKzj8njlP3s7MmUubAN+Y9+Hi2+8o2HTS7xFAFLW878H4ZKQZqk8BxVq3IA3zC3mCRMq7DroouGO1I7OZ4HvGPdVtwRg0ZxZ1mwqFj0/+aT9+9kDK519bhiisfx3fj3A8wP/F+77s2O7I0dvH9du/TyLA+sQtTuU175P5Inq+hcbH6SLzwawWL8xq+dft5xR8tL0gxu8Q8L7+j/l4ECAg93c4hJzFVL/wj30x6zpXZUAax3ymK71aDhhuzpUGUNdHUfYmY9x6bX7/8/VTwvuPb5LzL8//AVxLw+BFgxm1AAAAAElFTkSuQmCC')\n\nexport const ICON_192_BYTES = MARK_BYTES\nexport const ICON_512_BYTES = MARK_BYTES\n\nexport type StarterPwaIconSize = 192 | 512\n\n/** Bump when TanStack starter PWA PNGs change. */\nexport const STARTER_ICON_ASSET_REVISION = '4'\n\nexport function pngIconBytes(_size: StarterPwaIconSize): Uint8Array {\n\treturn MARK_BYTES\n}\n\nexport function iconPngResponse(size: StarterPwaIconSize): Response {\n\tconst body = pngIconBytes(size)\n\treturn new Response(body as BodyInit, {\n\t\theaders: {\n\t\t\t'content-type': 'image/png',\n\t\t\t'cache-control': 'public, max-age=86400',\n\t\t\t'content-length': String(body.byteLength),\n\t\t},\n\t})\n}\n\nexport function starterManifestIcons(appBasePath: string): Array<{\n\tsrc: string\n\tsizes: string\n\ttype: string\n\tpurpose: string\n}> {\n\tconst base = appBasePath.replace(/\\/$/, '')\n\tconst v = STARTER_ICON_ASSET_REVISION\n\treturn [\n\t\t{\n\t\t\tsrc: `${base}/icons/icon-192.png?v=${v}`,\n\t\t\tsizes: '192x192',\n\t\t\ttype: 'image/png',\n\t\t\tpurpose: 'any',\n\t\t},\n\t\t{\n\t\t\tsrc: `${base}/icons/icon-512.png?v=${v}`,\n\t\t\tsizes: '512x512',\n\t\t\ttype: 'image/png',\n\t\t\tpurpose: 'any',\n\t\t},\n\t]\n}\n",
	"src/index.ts": "/**\n * Package overview for the scaffolded TanStack Router package app.\n *\n * @example\n * import overview from 'kody:__PACKAGE_NAME__'\n * await overview()\n */\nexport default async function overview() {\n\treturn {\n\t\tname: '__PACKAGE_NAME__',\n\t\tscaffold: '@kentcdodds/package-app-kit/starter-tanstack',\n\t}\n}\n",
	"src/record-version.ts": "import { getAppVersion, setAppVersion, type AppVersion } from './version.ts'\n\n/**\n * Persist publish metadata in this app's packageStorage for About + update checks.\n * Do not re-export the kit's ./record-version — that writes the kit's storage and\n * fails packageStorage provenance when called as kody:@scope/app/record-version.\n *\n * @param input.sha - Full git commit SHA (required)\n * @param input.message - First-line commit message\n * @param input.committedAt - Commit author/committer ISO time\n * @param input.publishedAt - Publish time (defaults to now)\n * @returns Normalized AppVersion stored in packageStorage\n *\n * @example\n * import recordVersion from 'kody:__PACKAGE_NAME__/record-version'\n * await recordVersion({ sha: 'abc123', message: 'Ship' })\n */\nexport default async function recordVersion(input: {\n\tsha: string\n\tmessage?: string\n\tcommittedAt?: string\n\tpublishedAt?: string\n}): Promise<AppVersion> {\n\tif (!input?.sha) throw new Error('sha is required')\n\treturn await setAppVersion(input)\n}\n\n/**\n * Read the recorded version without writing.\n * Prefer this (or a relative import) for About / update-check server routes.\n *\n * @example\n * import { readRecordedVersion } from '../../src/record-version.ts'\n * const version = await readRecordedVersion()\n */\nexport async function readRecordedVersion(): Promise<AppVersion> {\n\treturn await getAppVersion()\n}\n\nexport type { AppVersion }\n",
	"src/version.ts": "import { packageStorage } from 'kody:runtime'\n\n/** Published app version metadata stored in packageStorage for About + update checks. */\nexport type AppVersion = {\n\tsha: string\n\tshortSha: string\n\tmessage: string\n\tcommittedAt: string\n\tpublishedAt: string\n}\n\nexport const APP_VERSION_STORAGE_KEY = 'app_version'\n\nexport function emptyVersion(): AppVersion {\n\treturn {\n\t\tsha: '',\n\t\tshortSha: '',\n\t\tmessage: '',\n\t\tcommittedAt: '',\n\t\tpublishedAt: '',\n\t}\n}\n\nexport function normalizeVersion(\n\tinput: Partial<AppVersion> & { sha?: string },\n): AppVersion {\n\tconst sha = String(input.sha ?? '').trim()\n\treturn {\n\t\tsha,\n\t\tshortSha: sha.slice(0, 7),\n\t\tmessage: String(input.message ?? '').split('\\n')[0] ?? '',\n\t\tcommittedAt: String(input.committedAt ?? ''),\n\t\tpublishedAt: String(input.publishedAt ?? ''),\n\t}\n}\n\nexport async function getAppVersion(): Promise<AppVersion> {\n\ttry {\n\t\tconst raw = await packageStorage().get(APP_VERSION_STORAGE_KEY)\n\t\tif (raw == null) return emptyVersion()\n\t\tif (typeof raw === 'string') {\n\t\t\treturn normalizeVersion(JSON.parse(raw) as Partial<AppVersion>)\n\t\t}\n\t\tif (typeof raw === 'object') {\n\t\t\treturn normalizeVersion(raw as Partial<AppVersion>)\n\t\t}\n\t\treturn emptyVersion()\n\t} catch {\n\t\treturn emptyVersion()\n\t}\n}\n\nexport async function setAppVersion(\n\tinput: Partial<AppVersion> & { sha: string },\n): Promise<AppVersion> {\n\tconst next = normalizeVersion({\n\t\t...(await getAppVersion()),\n\t\t...input,\n\t\tpublishedAt: input.publishedAt || new Date().toISOString(),\n\t})\n\tawait packageStorage().set(APP_VERSION_STORAGE_KEY, next)\n\treturn next\n}\n",
	"test/app.test.ts": "// Smoke tests that hit the app through `kody dev fetch` on the local mock\n// target. No prod data or credentials are touched. Add your own tests here.\nimport { test } from 'node:test'\nimport assert from 'node:assert/strict'\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst run = promisify(execFile)\n\nasync function devFetch(path: string) {\n\tconst { stdout } = await run(\n\t\t'npx',\n\t\t['--no-install', 'kody', 'dev', 'fetch', path, '--target', 'mock', '--json'],\n\t\t// kody dev fetch can hang after a bundle error; fail instead of waiting forever.\n\t\t{ maxBuffer: 10 * 1024 * 1024, timeout: 90_000 },\n\t)\n\tconst start = stdout.indexOf('{')\n\tassert.ok(start >= 0, `kody dev fetch ${path} printed no JSON:\\n${stdout}`)\n\treturn JSON.parse(stdout.slice(start)) as {\n\t\tstatus: number\n\t\theaders: Record<string, string>\n\t\tbody: string\n\t}\n}\n\ntest('GET /health returns ok', async () => {\n\tconst res = await devFetch('/health')\n\tassert.equal(res.status, 200)\n\tassert.deepEqual(JSON.parse(res.body), { ok: true })\n})\n\ntest('GET / renders HTML', async () => {\n\tconst res = await devFetch('/')\n\tassert.equal(res.status, 200)\n\tassert.match(res.headers['content-type'] ?? '', /text\\/html/)\n})\n",
	"tsconfig.json": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react-jsx\",\n\t\t\"jsxImportSource\": \"react\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"strict\": true,\n\t\t\"noEmit\": true,\n\t\t\"module\": \"esnext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"target\": \"es2022\",\n\t\t\"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"types\": [\"react\", \"react-dom\", \"node\"]\n\t}\n}\n",
	"types/kody.d.ts": "// Typecheck shim for Kody-provided virtual modules. The platform and\n// `kody dev` resolve `kody:runtime` and `kody:@owner/pkg/...` at bundle time;\n// tsc cannot, so shorthand declarations type those imports as `any`.\ndeclare module 'kody:runtime'\ndeclare module 'kody:*'\n",
}

export const TANSTACK_STARTER_TEMPLATE_RENAMES: Record<string, string> = {
	"package.template.json": "package.json",
	"README.template.md": "README.md",
	"AGENTS.template.md": "AGENTS.md",
}