denchclaw 2.1.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +4 -1
  2. package/apps/web/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  3. package/apps/web/.next/standalone/apps/web/.next/app-build-manifest.json +180 -166
  4. package/apps/web/.next/standalone/apps/web/.next/app-path-routes-manifest.json +34 -32
  5. package/apps/web/.next/standalone/apps/web/.next/build-manifest.json +5 -5
  6. package/apps/web/.next/standalone/apps/web/.next/react-loadable-manifest.json +8 -0
  7. package/apps/web/.next/standalone/apps/web/.next/required-server-files.json +4 -2
  8. package/apps/web/.next/standalone/apps/web/.next/routes-manifest.json +8 -0
  9. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  10. package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/route.js +61 -0
  11. package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/route.js.nft.json +1 -0
  12. package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/route_client-reference-manifest.js +1 -0
  13. package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/serve/[...path]/route.js +61 -0
  14. package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/serve/[...path]/route.js.nft.json +1 -0
  15. package/apps/web/.next/standalone/apps/web/.next/server/app/api/apps/serve/[...path]/route_client-reference-manifest.js +1 -0
  16. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/active/route_client-reference-manifest.js +1 -1
  17. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/route.js +2 -2
  18. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/route_client-reference-manifest.js +1 -1
  19. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stop/route_client-reference-manifest.js +1 -1
  20. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stream/route_client-reference-manifest.js +1 -1
  21. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/subagents/route.js +1 -1
  22. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/subagents/route.js.nft.json +1 -1
  23. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/subagents/route_client-reference-manifest.js +1 -1
  24. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/jobs/[jobId]/runs/route_client-reference-manifest.js +1 -1
  25. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/jobs/route_client-reference-manifest.js +1 -1
  26. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/runs/[sessionId]/route_client-reference-manifest.js +1 -1
  27. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/runs/search-transcript/route_client-reference-manifest.js +1 -1
  28. package/apps/web/.next/standalone/apps/web/.next/server/app/api/feedback/route_client-reference-manifest.js +1 -1
  29. package/apps/web/.next/standalone/apps/web/.next/server/app/api/memories/route_client-reference-manifest.js +1 -1
  30. package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/route.js +1 -1
  31. package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/route_client-reference-manifest.js +1 -1
  32. package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/switch/route.js +1 -1
  33. package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/switch/route_client-reference-manifest.js +1 -1
  34. package/apps/web/.next/standalone/apps/web/.next/server/app/api/sessions/[sessionId]/route_client-reference-manifest.js +1 -1
  35. package/apps/web/.next/standalone/apps/web/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  36. package/apps/web/.next/standalone/apps/web/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  37. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/messages/route_client-reference-manifest.js +1 -1
  38. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/route.js +1 -1
  39. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/route_client-reference-manifest.js +1 -1
  40. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/route.js +1 -1
  41. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/route_client-reference-manifest.js +1 -1
  42. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/assets/[...path]/route_client-reference-manifest.js +1 -1
  43. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/browse/route_client-reference-manifest.js +1 -1
  44. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/browse-file/route_client-reference-manifest.js +1 -1
  45. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/context/route_client-reference-manifest.js +1 -1
  46. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/copy/route_client-reference-manifest.js +1 -1
  47. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/db/introspect/route_client-reference-manifest.js +1 -1
  48. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/db/query/route_client-reference-manifest.js +1 -1
  49. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/delete/route.js +1 -1
  50. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/delete/route_client-reference-manifest.js +1 -1
  51. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/file/route_client-reference-manifest.js +1 -1
  52. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route.js +69 -3
  53. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route_client-reference-manifest.js +1 -1
  54. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/list/route.js +1 -1
  55. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/list/route_client-reference-manifest.js +1 -1
  56. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/mkdir/route_client-reference-manifest.js +1 -1
  57. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/move/route_client-reference-manifest.js +1 -1
  58. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/display-field/route_client-reference-manifest.js +1 -1
  59. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/[id]/route_client-reference-manifest.js +1 -1
  60. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/bulk-delete/route_client-reference-manifest.js +1 -1
  61. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/options/route_client-reference-manifest.js +1 -1
  62. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/route_client-reference-manifest.js +1 -1
  63. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route_client-reference-manifest.js +1 -1
  64. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/[fieldId]/route_client-reference-manifest.js +1 -1
  65. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/reorder/route_client-reference-manifest.js +1 -1
  66. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/route_client-reference-manifest.js +1 -1
  67. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/views/route_client-reference-manifest.js +1 -1
  68. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/open-file/route_client-reference-manifest.js +1 -1
  69. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/path-info/route_client-reference-manifest.js +1 -1
  70. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/query/route_client-reference-manifest.js +1 -1
  71. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/raw-file/route_client-reference-manifest.js +1 -1
  72. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/rename/route_client-reference-manifest.js +1 -1
  73. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/reports/execute/route_client-reference-manifest.js +1 -1
  74. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/search-index/route_client-reference-manifest.js +1 -1
  75. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/suggest-files/route_client-reference-manifest.js +1 -1
  76. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/switch/route.js +1 -1
  77. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/switch/route_client-reference-manifest.js +1 -1
  78. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/thumbnail/route_client-reference-manifest.js +1 -1
  79. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/tree/route.js +1 -1
  80. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/tree/route_client-reference-manifest.js +1 -1
  81. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/upload/route_client-reference-manifest.js +1 -1
  82. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/virtual-file/route_client-reference-manifest.js +1 -1
  83. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/watch/route_client-reference-manifest.js +1 -1
  84. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/write-binary/route_client-reference-manifest.js +1 -1
  85. package/apps/web/.next/standalone/apps/web/.next/server/app/page.js +8 -8
  86. package/apps/web/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  87. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
  88. package/apps/web/.next/standalone/apps/web/.next/server/app-paths-manifest.json +34 -32
  89. package/apps/web/.next/standalone/apps/web/.next/server/chunks/6426.js +7 -6
  90. package/apps/web/.next/standalone/apps/web/.next/server/chunks/6924.js +1 -1
  91. package/apps/web/.next/standalone/apps/web/.next/server/chunks/737.js +25 -0
  92. package/apps/web/.next/standalone/apps/web/.next/server/chunks/8899.js +1 -0
  93. package/apps/web/.next/standalone/apps/web/.next/server/functions-config-manifest.json +25 -23
  94. package/apps/web/.next/standalone/apps/web/.next/server/instrumentation.js +1 -0
  95. package/apps/web/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +1 -1
  96. package/apps/web/.next/standalone/apps/web/.next/server/middleware-react-loadable-manifest.js +1 -1
  97. package/apps/web/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  98. package/apps/web/.next/standalone/apps/web/.next/static/8TiFtXeWNbJK3kK2Uw7Dl/_buildManifest.js +1 -0
  99. package/apps/web/.next/standalone/apps/web/.next/static/chunks/1d2d5650.bf15d84c356fe97b.js +18 -0
  100. package/apps/web/.next/standalone/apps/web/.next/static/chunks/7858.c5e365bb10ae46df.js +1 -0
  101. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/apps/route-92144e0c11c84d4d.js +1 -0
  102. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/apps/serve/[...path]/route-92144e0c11c84d4d.js +1 -0
  103. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/active/route-92144e0c11c84d4d.js +1 -0
  104. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/route-92144e0c11c84d4d.js +1 -0
  105. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/stop/route-92144e0c11c84d4d.js +1 -0
  106. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/stream/route-92144e0c11c84d4d.js +1 -0
  107. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/subagents/route-92144e0c11c84d4d.js +1 -0
  108. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/jobs/[jobId]/runs/route-92144e0c11c84d4d.js +1 -0
  109. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/jobs/route-92144e0c11c84d4d.js +1 -0
  110. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/runs/[sessionId]/route-92144e0c11c84d4d.js +1 -0
  111. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/runs/search-transcript/route-92144e0c11c84d4d.js +1 -0
  112. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/feedback/route-92144e0c11c84d4d.js +1 -0
  113. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/memories/route-92144e0c11c84d4d.js +1 -0
  114. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/profiles/route-92144e0c11c84d4d.js +1 -0
  115. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/profiles/switch/route-92144e0c11c84d4d.js +1 -0
  116. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/sessions/[sessionId]/route-92144e0c11c84d4d.js +1 -0
  117. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/sessions/route-92144e0c11c84d4d.js +1 -0
  118. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/skills/route-92144e0c11c84d4d.js +1 -0
  119. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/web-sessions/[id]/messages/route-92144e0c11c84d4d.js +1 -0
  120. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/web-sessions/[id]/route-92144e0c11c84d4d.js +1 -0
  121. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/web-sessions/route-92144e0c11c84d4d.js +1 -0
  122. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/assets/[...path]/route-92144e0c11c84d4d.js +1 -0
  123. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/browse/route-92144e0c11c84d4d.js +1 -0
  124. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/browse-file/route-92144e0c11c84d4d.js +1 -0
  125. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/context/route-92144e0c11c84d4d.js +1 -0
  126. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/copy/route-92144e0c11c84d4d.js +1 -0
  127. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/db/introspect/route-92144e0c11c84d4d.js +1 -0
  128. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/db/query/route-92144e0c11c84d4d.js +1 -0
  129. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/delete/route-92144e0c11c84d4d.js +1 -0
  130. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/file/route-92144e0c11c84d4d.js +1 -0
  131. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/init/route-92144e0c11c84d4d.js +1 -0
  132. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/list/route-92144e0c11c84d4d.js +1 -0
  133. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/mkdir/route-92144e0c11c84d4d.js +1 -0
  134. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/move/route-92144e0c11c84d4d.js +1 -0
  135. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/display-field/route-92144e0c11c84d4d.js +1 -0
  136. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/[id]/route-92144e0c11c84d4d.js +1 -0
  137. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/bulk-delete/route-92144e0c11c84d4d.js +1 -0
  138. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/options/route-92144e0c11c84d4d.js +1 -0
  139. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/route-92144e0c11c84d4d.js +1 -0
  140. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route-92144e0c11c84d4d.js +1 -0
  141. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/route-92144e0c11c84d4d.js +1 -0
  142. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/reorder/route-92144e0c11c84d4d.js +1 -0
  143. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/route-92144e0c11c84d4d.js +1 -0
  144. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/views/route-92144e0c11c84d4d.js +1 -0
  145. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/open-file/route-92144e0c11c84d4d.js +1 -0
  146. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/path-info/route-92144e0c11c84d4d.js +1 -0
  147. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/query/route-92144e0c11c84d4d.js +1 -0
  148. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/raw-file/route-92144e0c11c84d4d.js +1 -0
  149. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/rename/route-92144e0c11c84d4d.js +1 -0
  150. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/reports/execute/route-92144e0c11c84d4d.js +1 -0
  151. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/search-index/route-92144e0c11c84d4d.js +1 -0
  152. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/suggest-files/route-92144e0c11c84d4d.js +1 -0
  153. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/switch/route-92144e0c11c84d4d.js +1 -0
  154. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/thumbnail/route-92144e0c11c84d4d.js +1 -0
  155. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/tree/route-92144e0c11c84d4d.js +1 -0
  156. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/upload/route-92144e0c11c84d4d.js +1 -0
  157. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/virtual-file/route-92144e0c11c84d4d.js +1 -0
  158. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/watch/route-92144e0c11c84d4d.js +1 -0
  159. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/write-binary/route-92144e0c11c84d4d.js +1 -0
  160. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/page-890b7921644e2001.js +1 -0
  161. package/apps/web/.next/standalone/apps/web/.next/static/chunks/webpack-b4ff13c7886e10c9.js +1 -0
  162. package/apps/web/.next/standalone/apps/web/.next/static/css/659eccb5db697b76.css +1 -0
  163. package/apps/web/.next/standalone/apps/web/.next/static/css/8e60990c0f80a584.css +1 -0
  164. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  165. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/index.js +52 -0
  166. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/shared/conout.js +11 -0
  167. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/terminal.js +190 -0
  168. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/unixTerminal.js +346 -0
  169. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/utils.js +39 -0
  170. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  171. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/windowsPtyAgent.js +320 -0
  172. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/lib/windowsTerminal.js +199 -0
  173. package/apps/web/.next/standalone/apps/web/node_modules/node-pty/package.json +64 -0
  174. package/apps/web/.next/standalone/apps/web/package.json +3 -0
  175. package/apps/web/.next/standalone/apps/web/server.js +1 -1
  176. package/apps/web/.next/standalone/package.json +1 -1
  177. package/apps/web/.next/static/8TiFtXeWNbJK3kK2Uw7Dl/_buildManifest.js +1 -0
  178. package/apps/web/.next/static/chunks/1d2d5650.bf15d84c356fe97b.js +18 -0
  179. package/apps/web/.next/static/chunks/7858.c5e365bb10ae46df.js +1 -0
  180. package/apps/web/.next/static/chunks/app/api/apps/route-92144e0c11c84d4d.js +1 -0
  181. package/apps/web/.next/static/chunks/app/api/apps/serve/[...path]/route-92144e0c11c84d4d.js +1 -0
  182. package/apps/web/.next/static/chunks/app/api/chat/active/route-92144e0c11c84d4d.js +1 -0
  183. package/apps/web/.next/static/chunks/app/api/chat/route-92144e0c11c84d4d.js +1 -0
  184. package/apps/web/.next/static/chunks/app/api/chat/stop/route-92144e0c11c84d4d.js +1 -0
  185. package/apps/web/.next/static/chunks/app/api/chat/stream/route-92144e0c11c84d4d.js +1 -0
  186. package/apps/web/.next/static/chunks/app/api/chat/subagents/route-92144e0c11c84d4d.js +1 -0
  187. package/apps/web/.next/static/chunks/app/api/cron/jobs/[jobId]/runs/route-92144e0c11c84d4d.js +1 -0
  188. package/apps/web/.next/static/chunks/app/api/cron/jobs/route-92144e0c11c84d4d.js +1 -0
  189. package/apps/web/.next/static/chunks/app/api/cron/runs/[sessionId]/route-92144e0c11c84d4d.js +1 -0
  190. package/apps/web/.next/static/chunks/app/api/cron/runs/search-transcript/route-92144e0c11c84d4d.js +1 -0
  191. package/apps/web/.next/static/chunks/app/api/feedback/route-92144e0c11c84d4d.js +1 -0
  192. package/apps/web/.next/static/chunks/app/api/memories/route-92144e0c11c84d4d.js +1 -0
  193. package/apps/web/.next/static/chunks/app/api/profiles/route-92144e0c11c84d4d.js +1 -0
  194. package/apps/web/.next/static/chunks/app/api/profiles/switch/route-92144e0c11c84d4d.js +1 -0
  195. package/apps/web/.next/static/chunks/app/api/sessions/[sessionId]/route-92144e0c11c84d4d.js +1 -0
  196. package/apps/web/.next/static/chunks/app/api/sessions/route-92144e0c11c84d4d.js +1 -0
  197. package/apps/web/.next/static/chunks/app/api/skills/route-92144e0c11c84d4d.js +1 -0
  198. package/apps/web/.next/static/chunks/app/api/web-sessions/[id]/messages/route-92144e0c11c84d4d.js +1 -0
  199. package/apps/web/.next/static/chunks/app/api/web-sessions/[id]/route-92144e0c11c84d4d.js +1 -0
  200. package/apps/web/.next/static/chunks/app/api/web-sessions/route-92144e0c11c84d4d.js +1 -0
  201. package/apps/web/.next/static/chunks/app/api/workspace/assets/[...path]/route-92144e0c11c84d4d.js +1 -0
  202. package/apps/web/.next/static/chunks/app/api/workspace/browse/route-92144e0c11c84d4d.js +1 -0
  203. package/apps/web/.next/static/chunks/app/api/workspace/browse-file/route-92144e0c11c84d4d.js +1 -0
  204. package/apps/web/.next/static/chunks/app/api/workspace/context/route-92144e0c11c84d4d.js +1 -0
  205. package/apps/web/.next/static/chunks/app/api/workspace/copy/route-92144e0c11c84d4d.js +1 -0
  206. package/apps/web/.next/static/chunks/app/api/workspace/db/introspect/route-92144e0c11c84d4d.js +1 -0
  207. package/apps/web/.next/static/chunks/app/api/workspace/db/query/route-92144e0c11c84d4d.js +1 -0
  208. package/apps/web/.next/static/chunks/app/api/workspace/delete/route-92144e0c11c84d4d.js +1 -0
  209. package/apps/web/.next/static/chunks/app/api/workspace/file/route-92144e0c11c84d4d.js +1 -0
  210. package/apps/web/.next/static/chunks/app/api/workspace/init/route-92144e0c11c84d4d.js +1 -0
  211. package/apps/web/.next/static/chunks/app/api/workspace/list/route-92144e0c11c84d4d.js +1 -0
  212. package/apps/web/.next/static/chunks/app/api/workspace/mkdir/route-92144e0c11c84d4d.js +1 -0
  213. package/apps/web/.next/static/chunks/app/api/workspace/move/route-92144e0c11c84d4d.js +1 -0
  214. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/display-field/route-92144e0c11c84d4d.js +1 -0
  215. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/[id]/route-92144e0c11c84d4d.js +1 -0
  216. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/bulk-delete/route-92144e0c11c84d4d.js +1 -0
  217. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/options/route-92144e0c11c84d4d.js +1 -0
  218. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/route-92144e0c11c84d4d.js +1 -0
  219. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route-92144e0c11c84d4d.js +1 -0
  220. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/route-92144e0c11c84d4d.js +1 -0
  221. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/reorder/route-92144e0c11c84d4d.js +1 -0
  222. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/route-92144e0c11c84d4d.js +1 -0
  223. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/views/route-92144e0c11c84d4d.js +1 -0
  224. package/apps/web/.next/static/chunks/app/api/workspace/open-file/route-92144e0c11c84d4d.js +1 -0
  225. package/apps/web/.next/static/chunks/app/api/workspace/path-info/route-92144e0c11c84d4d.js +1 -0
  226. package/apps/web/.next/static/chunks/app/api/workspace/query/route-92144e0c11c84d4d.js +1 -0
  227. package/apps/web/.next/static/chunks/app/api/workspace/raw-file/route-92144e0c11c84d4d.js +1 -0
  228. package/apps/web/.next/static/chunks/app/api/workspace/rename/route-92144e0c11c84d4d.js +1 -0
  229. package/apps/web/.next/static/chunks/app/api/workspace/reports/execute/route-92144e0c11c84d4d.js +1 -0
  230. package/apps/web/.next/static/chunks/app/api/workspace/search-index/route-92144e0c11c84d4d.js +1 -0
  231. package/apps/web/.next/static/chunks/app/api/workspace/suggest-files/route-92144e0c11c84d4d.js +1 -0
  232. package/apps/web/.next/static/chunks/app/api/workspace/switch/route-92144e0c11c84d4d.js +1 -0
  233. package/apps/web/.next/static/chunks/app/api/workspace/thumbnail/route-92144e0c11c84d4d.js +1 -0
  234. package/apps/web/.next/static/chunks/app/api/workspace/tree/route-92144e0c11c84d4d.js +1 -0
  235. package/apps/web/.next/static/chunks/app/api/workspace/upload/route-92144e0c11c84d4d.js +1 -0
  236. package/apps/web/.next/static/chunks/app/api/workspace/virtual-file/route-92144e0c11c84d4d.js +1 -0
  237. package/apps/web/.next/static/chunks/app/api/workspace/watch/route-92144e0c11c84d4d.js +1 -0
  238. package/apps/web/.next/static/chunks/app/api/workspace/write-binary/route-92144e0c11c84d4d.js +1 -0
  239. package/apps/web/.next/static/chunks/app/page-890b7921644e2001.js +1 -0
  240. package/apps/web/.next/static/chunks/webpack-b4ff13c7886e10c9.js +1 -0
  241. package/apps/web/.next/static/css/659eccb5db697b76.css +1 -0
  242. package/apps/web/.next/static/css/8e60990c0f80a584.css +1 -0
  243. package/dist/entry.js +1 -1
  244. package/dist/{program-B44Xn_sp.js → program-VYLGlGTu.js} +87 -4
  245. package/dist/{run-main-CesqAppu.js → run-main-BF0xKDV2.js} +1 -1
  246. package/package.json +1 -1
  247. package/skills/app-builder/SKILL.md +2335 -0
  248. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/active/route-ff99b057da8a00ed.js +0 -1
  249. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/route-ff99b057da8a00ed.js +0 -1
  250. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/stop/route-ff99b057da8a00ed.js +0 -1
  251. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/stream/route-ff99b057da8a00ed.js +0 -1
  252. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/chat/subagents/route-ff99b057da8a00ed.js +0 -1
  253. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/jobs/[jobId]/runs/route-ff99b057da8a00ed.js +0 -1
  254. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/jobs/route-ff99b057da8a00ed.js +0 -1
  255. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/runs/[sessionId]/route-ff99b057da8a00ed.js +0 -1
  256. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/cron/runs/search-transcript/route-ff99b057da8a00ed.js +0 -1
  257. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/feedback/route-ff99b057da8a00ed.js +0 -1
  258. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/memories/route-ff99b057da8a00ed.js +0 -1
  259. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/profiles/route-ff99b057da8a00ed.js +0 -1
  260. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/profiles/switch/route-ff99b057da8a00ed.js +0 -1
  261. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/sessions/[sessionId]/route-ff99b057da8a00ed.js +0 -1
  262. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/sessions/route-ff99b057da8a00ed.js +0 -1
  263. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/skills/route-ff99b057da8a00ed.js +0 -1
  264. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/web-sessions/[id]/messages/route-ff99b057da8a00ed.js +0 -1
  265. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/web-sessions/[id]/route-ff99b057da8a00ed.js +0 -1
  266. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/web-sessions/route-ff99b057da8a00ed.js +0 -1
  267. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/assets/[...path]/route-ff99b057da8a00ed.js +0 -1
  268. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/browse/route-ff99b057da8a00ed.js +0 -1
  269. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/browse-file/route-ff99b057da8a00ed.js +0 -1
  270. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/context/route-ff99b057da8a00ed.js +0 -1
  271. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/copy/route-ff99b057da8a00ed.js +0 -1
  272. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/db/introspect/route-ff99b057da8a00ed.js +0 -1
  273. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/db/query/route-ff99b057da8a00ed.js +0 -1
  274. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/delete/route-ff99b057da8a00ed.js +0 -1
  275. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/file/route-ff99b057da8a00ed.js +0 -1
  276. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/init/route-ff99b057da8a00ed.js +0 -1
  277. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/list/route-ff99b057da8a00ed.js +0 -1
  278. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/mkdir/route-ff99b057da8a00ed.js +0 -1
  279. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/move/route-ff99b057da8a00ed.js +0 -1
  280. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/display-field/route-ff99b057da8a00ed.js +0 -1
  281. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/[id]/route-ff99b057da8a00ed.js +0 -1
  282. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/bulk-delete/route-ff99b057da8a00ed.js +0 -1
  283. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/options/route-ff99b057da8a00ed.js +0 -1
  284. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/route-ff99b057da8a00ed.js +0 -1
  285. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route-ff99b057da8a00ed.js +0 -1
  286. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/route-ff99b057da8a00ed.js +0 -1
  287. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/reorder/route-ff99b057da8a00ed.js +0 -1
  288. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/route-ff99b057da8a00ed.js +0 -1
  289. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/views/route-ff99b057da8a00ed.js +0 -1
  290. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/open-file/route-ff99b057da8a00ed.js +0 -1
  291. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/path-info/route-ff99b057da8a00ed.js +0 -1
  292. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/query/route-ff99b057da8a00ed.js +0 -1
  293. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/raw-file/route-ff99b057da8a00ed.js +0 -1
  294. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/rename/route-ff99b057da8a00ed.js +0 -1
  295. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/reports/execute/route-ff99b057da8a00ed.js +0 -1
  296. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/search-index/route-ff99b057da8a00ed.js +0 -1
  297. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/suggest-files/route-ff99b057da8a00ed.js +0 -1
  298. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/switch/route-ff99b057da8a00ed.js +0 -1
  299. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/thumbnail/route-ff99b057da8a00ed.js +0 -1
  300. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/tree/route-ff99b057da8a00ed.js +0 -1
  301. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/upload/route-ff99b057da8a00ed.js +0 -1
  302. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/virtual-file/route-ff99b057da8a00ed.js +0 -1
  303. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/watch/route-ff99b057da8a00ed.js +0 -1
  304. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/api/workspace/write-binary/route-ff99b057da8a00ed.js +0 -1
  305. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/page-69792dc72cd99a61.js +0 -1
  306. package/apps/web/.next/standalone/apps/web/.next/static/chunks/webpack-f4003099d25f0299.js +0 -1
  307. package/apps/web/.next/standalone/apps/web/.next/static/css/5f21e02e203472d6.css +0 -1
  308. package/apps/web/.next/standalone/apps/web/.next/static/vpW04s7HdH5k8Nb2BR6u3/_buildManifest.js +0 -1
  309. package/apps/web/.next/static/chunks/app/api/chat/active/route-ff99b057da8a00ed.js +0 -1
  310. package/apps/web/.next/static/chunks/app/api/chat/route-ff99b057da8a00ed.js +0 -1
  311. package/apps/web/.next/static/chunks/app/api/chat/stop/route-ff99b057da8a00ed.js +0 -1
  312. package/apps/web/.next/static/chunks/app/api/chat/stream/route-ff99b057da8a00ed.js +0 -1
  313. package/apps/web/.next/static/chunks/app/api/chat/subagents/route-ff99b057da8a00ed.js +0 -1
  314. package/apps/web/.next/static/chunks/app/api/cron/jobs/[jobId]/runs/route-ff99b057da8a00ed.js +0 -1
  315. package/apps/web/.next/static/chunks/app/api/cron/jobs/route-ff99b057da8a00ed.js +0 -1
  316. package/apps/web/.next/static/chunks/app/api/cron/runs/[sessionId]/route-ff99b057da8a00ed.js +0 -1
  317. package/apps/web/.next/static/chunks/app/api/cron/runs/search-transcript/route-ff99b057da8a00ed.js +0 -1
  318. package/apps/web/.next/static/chunks/app/api/feedback/route-ff99b057da8a00ed.js +0 -1
  319. package/apps/web/.next/static/chunks/app/api/memories/route-ff99b057da8a00ed.js +0 -1
  320. package/apps/web/.next/static/chunks/app/api/profiles/route-ff99b057da8a00ed.js +0 -1
  321. package/apps/web/.next/static/chunks/app/api/profiles/switch/route-ff99b057da8a00ed.js +0 -1
  322. package/apps/web/.next/static/chunks/app/api/sessions/[sessionId]/route-ff99b057da8a00ed.js +0 -1
  323. package/apps/web/.next/static/chunks/app/api/sessions/route-ff99b057da8a00ed.js +0 -1
  324. package/apps/web/.next/static/chunks/app/api/skills/route-ff99b057da8a00ed.js +0 -1
  325. package/apps/web/.next/static/chunks/app/api/web-sessions/[id]/messages/route-ff99b057da8a00ed.js +0 -1
  326. package/apps/web/.next/static/chunks/app/api/web-sessions/[id]/route-ff99b057da8a00ed.js +0 -1
  327. package/apps/web/.next/static/chunks/app/api/web-sessions/route-ff99b057da8a00ed.js +0 -1
  328. package/apps/web/.next/static/chunks/app/api/workspace/assets/[...path]/route-ff99b057da8a00ed.js +0 -1
  329. package/apps/web/.next/static/chunks/app/api/workspace/browse/route-ff99b057da8a00ed.js +0 -1
  330. package/apps/web/.next/static/chunks/app/api/workspace/browse-file/route-ff99b057da8a00ed.js +0 -1
  331. package/apps/web/.next/static/chunks/app/api/workspace/context/route-ff99b057da8a00ed.js +0 -1
  332. package/apps/web/.next/static/chunks/app/api/workspace/copy/route-ff99b057da8a00ed.js +0 -1
  333. package/apps/web/.next/static/chunks/app/api/workspace/db/introspect/route-ff99b057da8a00ed.js +0 -1
  334. package/apps/web/.next/static/chunks/app/api/workspace/db/query/route-ff99b057da8a00ed.js +0 -1
  335. package/apps/web/.next/static/chunks/app/api/workspace/delete/route-ff99b057da8a00ed.js +0 -1
  336. package/apps/web/.next/static/chunks/app/api/workspace/file/route-ff99b057da8a00ed.js +0 -1
  337. package/apps/web/.next/static/chunks/app/api/workspace/init/route-ff99b057da8a00ed.js +0 -1
  338. package/apps/web/.next/static/chunks/app/api/workspace/list/route-ff99b057da8a00ed.js +0 -1
  339. package/apps/web/.next/static/chunks/app/api/workspace/mkdir/route-ff99b057da8a00ed.js +0 -1
  340. package/apps/web/.next/static/chunks/app/api/workspace/move/route-ff99b057da8a00ed.js +0 -1
  341. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/display-field/route-ff99b057da8a00ed.js +0 -1
  342. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/[id]/route-ff99b057da8a00ed.js +0 -1
  343. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/bulk-delete/route-ff99b057da8a00ed.js +0 -1
  344. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/options/route-ff99b057da8a00ed.js +0 -1
  345. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/entries/route-ff99b057da8a00ed.js +0 -1
  346. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route-ff99b057da8a00ed.js +0 -1
  347. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/[fieldId]/route-ff99b057da8a00ed.js +0 -1
  348. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/fields/reorder/route-ff99b057da8a00ed.js +0 -1
  349. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/route-ff99b057da8a00ed.js +0 -1
  350. package/apps/web/.next/static/chunks/app/api/workspace/objects/[name]/views/route-ff99b057da8a00ed.js +0 -1
  351. package/apps/web/.next/static/chunks/app/api/workspace/open-file/route-ff99b057da8a00ed.js +0 -1
  352. package/apps/web/.next/static/chunks/app/api/workspace/path-info/route-ff99b057da8a00ed.js +0 -1
  353. package/apps/web/.next/static/chunks/app/api/workspace/query/route-ff99b057da8a00ed.js +0 -1
  354. package/apps/web/.next/static/chunks/app/api/workspace/raw-file/route-ff99b057da8a00ed.js +0 -1
  355. package/apps/web/.next/static/chunks/app/api/workspace/rename/route-ff99b057da8a00ed.js +0 -1
  356. package/apps/web/.next/static/chunks/app/api/workspace/reports/execute/route-ff99b057da8a00ed.js +0 -1
  357. package/apps/web/.next/static/chunks/app/api/workspace/search-index/route-ff99b057da8a00ed.js +0 -1
  358. package/apps/web/.next/static/chunks/app/api/workspace/suggest-files/route-ff99b057da8a00ed.js +0 -1
  359. package/apps/web/.next/static/chunks/app/api/workspace/switch/route-ff99b057da8a00ed.js +0 -1
  360. package/apps/web/.next/static/chunks/app/api/workspace/thumbnail/route-ff99b057da8a00ed.js +0 -1
  361. package/apps/web/.next/static/chunks/app/api/workspace/tree/route-ff99b057da8a00ed.js +0 -1
  362. package/apps/web/.next/static/chunks/app/api/workspace/upload/route-ff99b057da8a00ed.js +0 -1
  363. package/apps/web/.next/static/chunks/app/api/workspace/virtual-file/route-ff99b057da8a00ed.js +0 -1
  364. package/apps/web/.next/static/chunks/app/api/workspace/watch/route-ff99b057da8a00ed.js +0 -1
  365. package/apps/web/.next/static/chunks/app/api/workspace/write-binary/route-ff99b057da8a00ed.js +0 -1
  366. package/apps/web/.next/static/chunks/app/page-69792dc72cd99a61.js +0 -1
  367. package/apps/web/.next/static/chunks/webpack-f4003099d25f0299.js +0 -1
  368. package/apps/web/.next/static/css/5f21e02e203472d6.css +0 -1
  369. package/apps/web/.next/static/vpW04s7HdH5k8Nb2BR6u3/_buildManifest.js +0 -1
  370. /package/apps/web/.next/standalone/apps/web/.next/static/{vpW04s7HdH5k8Nb2BR6u3 → 8TiFtXeWNbJK3kK2Uw7Dl}/_ssgManifest.js +0 -0
  371. /package/apps/web/.next/static/{vpW04s7HdH5k8Nb2BR6u3 → 8TiFtXeWNbJK3kK2Uw7Dl}/_ssgManifest.js +0 -0
@@ -0,0 +1,2335 @@
1
+ ---
2
+ name: app-builder
3
+ description: Build and manage DenchClaw apps — self-contained web applications that run inside the workspace with access to DuckDB data and the DenchClaw bridge API. Covers static HTML apps, 2D games with p5.js, 3D experiences with Three.js, data dashboards, interactive tools, and more.
4
+ metadata: { "openclaw": { "inject": true, "always": true, "emoji": "🔨" } }
5
+ ---
6
+
7
+ # App Builder
8
+
9
+ You can build **Dench Apps** — self-contained web applications that run inside DenchClaw's workspace. Apps appear in the sidebar with their own icon and name, and open as tabs in the main content area. They run in a sandboxed iframe with `allow-same-origin allow-scripts allow-popups allow-forms`.
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ 1. [App Structure](#app-structure)
16
+ 2. [Manifest Reference](#manifest-reference)
17
+ 3. [Bridge API Reference](#bridge-api-reference)
18
+ 4. [Theme & Styling System](#theme--styling-system)
19
+ 5. [Loading External Libraries via CDN](#loading-external-libraries-via-cdn)
20
+ 6. [2D Games with p5.js](#2d-games-with-p5js)
21
+ 7. [3D Games & Experiences with Three.js](#3d-games--experiences-with-threejs)
22
+ 8. [Data Dashboards & Visualization](#data-dashboards--visualization)
23
+ 9. [Interactive Tools & Utilities](#interactive-tools--utilities)
24
+ 10. [Multi-File App Organization](#multi-file-app-organization)
25
+ 11. [Asset Management](#asset-management)
26
+ 12. [Performance & Best Practices](#performance--best-practices)
27
+ 13. [Error Handling Patterns](#error-handling-patterns)
28
+ 14. [Full Example Apps](#full-example-apps)
29
+
30
+ ---
31
+
32
+ ## App Structure
33
+
34
+ Every app is a folder ending in `.dench.app/`. The default location is `{{WORKSPACE_PATH}}/apps/`, but apps can live anywhere in the workspace.
35
+
36
+ ```
37
+ apps/
38
+ my-app.dench.app/
39
+ .dench.yaml # Required manifest
40
+ index.html # Entry point
41
+ style.css # Styles (optional, can inline)
42
+ app.js # Logic (optional, can inline)
43
+ assets/ # Images, sounds, models, etc.
44
+ sprite.png
45
+ bg-music.mp3
46
+ lib/ # Vendored libraries (optional)
47
+ p5.min.js
48
+ ```
49
+
50
+ ### Critical Rules
51
+
52
+ - The folder name MUST end with `.dench.app`
53
+ - The `.dench.yaml` manifest is REQUIRED inside every `.dench.app` folder
54
+ - The entry HTML file gets the bridge SDK (`window.dench`) auto-injected before `</head>`
55
+ - All file paths within the app are relative to the `.dench.app` folder root
56
+ - The app is served at `/api/apps/serve/<appPath>/<filePath>` — relative references (CSS, JS, images) resolve correctly
57
+ - Apps run in an iframe sandbox: `allow-same-origin allow-scripts allow-popups allow-forms`
58
+
59
+ ---
60
+
61
+ ## Manifest Reference
62
+
63
+ Every `.dench.app` folder MUST contain a `.dench.yaml` manifest.
64
+
65
+ ### Full Schema
66
+
67
+ ```yaml
68
+ name: "My App" # Required. Display name shown in sidebar and tab bar
69
+ description: "What this app does" # Optional. Shown in tooltips and app info
70
+ icon: "gamepad-2" # Optional. Lucide icon name OR relative path to image
71
+ version: "1.0.0" # Optional. Shown as badge in app header
72
+ author: "agent" # Optional. Creator attribution
73
+ entry: "index.html" # Optional. Main entry point (default: index.html)
74
+ runtime: "static" # Optional. static | esbuild | build (default: static)
75
+
76
+ permissions: # Optional. List of bridge API permissions
77
+ - database # Can query workspace DuckDB via window.dench.db
78
+ - files # Can read workspace files via window.dench.files
79
+ ```
80
+
81
+ ### Runtime Modes
82
+
83
+ | Mode | When to Use | How It Works |
84
+ |------|-------------|--------------|
85
+ | `static` | Vanilla HTML/CSS/JS apps, CDN-loaded libraries, games, dashboards | Serves files directly. **Use this by default for everything.** |
86
+ | `esbuild` | React/TSX apps without npm dependencies | Server-side esbuild transpiles JSX/TSX on load. Requires `esbuild.entry` and `esbuild.jsx` fields. |
87
+ | `build` | Complex apps with npm dependencies (rare) | Runs `build.install` then `build.command`. Serves from `build.output` directory. |
88
+
89
+ **Always default to `static` runtime.** It handles p5.js, Three.js, D3.js, Chart.js, and any CDN-loaded library perfectly. Only use `esbuild` or `build` when the user explicitly asks for React/TSX or npm-based tooling.
90
+
91
+ ### Icon Support
92
+
93
+ The `icon` field accepts:
94
+
95
+ 1. **A Lucide icon name** (string): `"gamepad-2"`, `"bar-chart-3"`, `"users"`, `"rocket"`, `"calculator"`, `"box"`, `"palette"`
96
+ 2. **A relative path** to a square image file: `"icon.png"`, `"assets/logo.svg"`
97
+
98
+ Supported image formats: PNG, SVG, JPG, JPEG, WebP. Use square aspect ratio (128x128px or larger).
99
+
100
+ ### Choosing Permissions
101
+
102
+ | Permission | Grants | Use When |
103
+ |------------|--------|----------|
104
+ | `database` | `window.dench.db.query()` and `window.dench.db.execute()` | App reads/writes workspace DuckDB data |
105
+ | `files` | `window.dench.files.read()` and `window.dench.files.list()` | App reads workspace files or directory tree |
106
+
107
+ Only request what you need. A game with no data access needs no permissions at all.
108
+
109
+ ---
110
+
111
+ ## Bridge API Reference
112
+
113
+ The bridge SDK is auto-injected into every app's HTML. It provides `window.dench` with the following methods. All methods return Promises with a 30-second timeout.
114
+
115
+ ### Database Access (`database` permission required)
116
+
117
+ ```javascript
118
+ // Run a SELECT query — returns { rows: [...], columns: [...] }
119
+ const result = await window.dench.db.query("SELECT * FROM objects");
120
+ console.log(result.rows); // Array of row objects
121
+ console.log(result.columns); // Array of column name strings
122
+
123
+ // Run a mutation (INSERT, UPDATE, DELETE, CREATE TABLE, etc.)
124
+ await window.dench.db.execute("INSERT INTO ...");
125
+
126
+ // Parameterized-style queries (use string interpolation carefully)
127
+ const objectName = "people";
128
+ const entries = await window.dench.db.query(
129
+ `SELECT * FROM entries WHERE object_id = (SELECT id FROM objects WHERE name = '${objectName}')`
130
+ );
131
+ ```
132
+
133
+ ### File Access (`files` permission required)
134
+
135
+ ```javascript
136
+ // Read a workspace file by relative path
137
+ const fileContent = await window.dench.files.read("path/to/file.md");
138
+
139
+ // List the workspace directory tree
140
+ const tree = await window.dench.files.list();
141
+ // Returns nested tree structure: { name, path, type, children? }[]
142
+ ```
143
+
144
+ ### App Utilities (no permission required)
145
+
146
+ ```javascript
147
+ // Get the app's own parsed manifest
148
+ const manifest = await window.dench.app.getManifest();
149
+ // Returns: { name, description, icon, version, author, entry, runtime, permissions }
150
+
151
+ // Get current DenchClaw UI theme
152
+ const theme = await window.dench.app.getTheme();
153
+ // Returns: "dark" or "light"
154
+ ```
155
+
156
+ ### Agent Communication (no permission required)
157
+
158
+ ```javascript
159
+ // Send a message to the DenchClaw agent (triggers a chat message)
160
+ await window.dench.agent.send("Analyze the data in the people table");
161
+ ```
162
+
163
+ ### Waiting for Bridge Readiness
164
+
165
+ The bridge script is injected into `<head>`, so it's available by the time your scripts run. However, if you use `defer` or `type="module"` scripts, you can safely access `window.dench` immediately since module scripts run after the document is parsed.
166
+
167
+ ```javascript
168
+ // Safe pattern for any script loading order
169
+ function whenDenchReady(fn) {
170
+ if (window.dench) return fn();
171
+ const check = setInterval(() => {
172
+ if (window.dench) { clearInterval(check); fn(); }
173
+ }, 50);
174
+ }
175
+
176
+ whenDenchReady(async () => {
177
+ const theme = await window.dench.app.getTheme();
178
+ document.body.className = theme;
179
+ });
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Theme & Styling System
185
+
186
+ Apps should respect the DenchClaw theme. The bridge provides the current theme ("dark" or "light"). Build your CSS to support both.
187
+
188
+ ### Recommended Base Styles
189
+
190
+ ```css
191
+ * { box-sizing: border-box; margin: 0; padding: 0; }
192
+
193
+ body {
194
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
195
+ 'Helvetica Neue', Arial, sans-serif;
196
+ line-height: 1.5;
197
+ -webkit-font-smoothing: antialiased;
198
+ -moz-osx-font-smoothing: grayscale;
199
+ transition: background-color 0.2s, color 0.2s;
200
+ }
201
+
202
+ body.dark {
203
+ --app-bg: #0f0f1a;
204
+ --app-surface: #1a1a2e;
205
+ --app-surface-hover: #252540;
206
+ --app-border: #2a2a45;
207
+ --app-text: #e8e8f0;
208
+ --app-text-muted: #8888a8;
209
+ --app-accent: #6366f1;
210
+ --app-accent-hover: #818cf8;
211
+ --app-success: #22c55e;
212
+ --app-warning: #f59e0b;
213
+ --app-error: #ef4444;
214
+ background: var(--app-bg);
215
+ color: var(--app-text);
216
+ }
217
+
218
+ body.light {
219
+ --app-bg: #ffffff;
220
+ --app-surface: #f8f9fa;
221
+ --app-surface-hover: #f0f1f3;
222
+ --app-border: #e2e4e8;
223
+ --app-text: #1a1a2e;
224
+ --app-text-muted: #6b7280;
225
+ --app-accent: #6366f1;
226
+ --app-accent-hover: #4f46e5;
227
+ --app-success: #16a34a;
228
+ --app-warning: #d97706;
229
+ --app-error: #dc2626;
230
+ background: var(--app-bg);
231
+ color: var(--app-text);
232
+ }
233
+ ```
234
+
235
+ ### Theme Initialization
236
+
237
+ Always apply the theme as the first action in your app:
238
+
239
+ ```javascript
240
+ async function initTheme() {
241
+ try {
242
+ const theme = await window.dench.app.getTheme();
243
+ document.body.className = theme;
244
+ } catch {
245
+ document.body.className = 'dark';
246
+ }
247
+ }
248
+ initTheme();
249
+ ```
250
+
251
+ ### Canvas-Based Apps (Games)
252
+
253
+ For p5.js, Three.js, or any canvas-based app, set the canvas background based on theme and make sure the body has no scrollbars:
254
+
255
+ ```css
256
+ body {
257
+ margin: 0;
258
+ padding: 0;
259
+ overflow: hidden;
260
+ width: 100vw;
261
+ height: 100vh;
262
+ }
263
+
264
+ canvas {
265
+ display: block;
266
+ }
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Loading External Libraries via CDN
272
+
273
+ Since apps use `runtime: "static"`, load libraries via CDN `<script>` tags. The app iframe allows external script loading.
274
+
275
+ ### Recommended CDNs
276
+
277
+ Use **unpkg** or **cdnjs** for reliability:
278
+
279
+ ```html
280
+ <!-- p5.js -->
281
+ <script src="https://unpkg.com/p5@1/lib/p5.min.js"></script>
282
+
283
+ <!-- Three.js -->
284
+ <script type="importmap">
285
+ {
286
+ "imports": {
287
+ "three": "https://unpkg.com/three@0.170/build/three.module.js",
288
+ "three/addons/": "https://unpkg.com/three@0.170/examples/jsm/"
289
+ }
290
+ }
291
+ </script>
292
+
293
+ <!-- D3.js -->
294
+ <script src="https://unpkg.com/d3@7/dist/d3.min.js"></script>
295
+
296
+ <!-- Chart.js -->
297
+ <script src="https://unpkg.com/chart.js@4/dist/chart.umd.min.js"></script>
298
+
299
+ <!-- Tone.js (audio) -->
300
+ <script src="https://unpkg.com/tone@15/build/Tone.js"></script>
301
+
302
+ <!-- Matter.js (2D physics) -->
303
+ <script src="https://unpkg.com/matter-js@0.20/build/matter.min.js"></script>
304
+
305
+ <!-- cannon-es (3D physics) -->
306
+ <script type="module">
307
+ import * as CANNON from 'https://unpkg.com/cannon-es@0.20/dist/cannon-es.js';
308
+ </script>
309
+
310
+ <!-- GSAP (animation) -->
311
+ <script src="https://unpkg.com/gsap@3/dist/gsap.min.js"></script>
312
+
313
+ <!-- Howler.js (audio) -->
314
+ <script src="https://unpkg.com/howler@2/dist/howler.min.js"></script>
315
+ ```
316
+
317
+ ### Import Maps for ES Modules
318
+
319
+ For Three.js and other module-based libraries, use import maps:
320
+
321
+ ```html
322
+ <script type="importmap">
323
+ {
324
+ "imports": {
325
+ "three": "https://unpkg.com/three@0.170/build/three.module.js",
326
+ "three/addons/": "https://unpkg.com/three@0.170/examples/jsm/"
327
+ }
328
+ }
329
+ </script>
330
+ <script type="module" src="app.js"></script>
331
+ ```
332
+
333
+ ---
334
+
335
+ ## 2D Games with p5.js
336
+
337
+ **Always use p5.js for 2D games, simulations, generative art, and interactive 2D experiences.** p5.js is the default choice for anything 2D unless the user specifically requests something else.
338
+
339
+ ### When to Use p5.js
340
+
341
+ - 2D games (platformer, puzzle, arcade, card games, board games)
342
+ - Generative art and creative coding
343
+ - Physics simulations and particle systems
344
+ - Interactive data visualizations with animation
345
+ - Educational simulations and demonstrations
346
+ - Drawing and painting tools
347
+ - Any 2D canvas-based interactive experience
348
+
349
+ ### p5.js App Template
350
+
351
+ ```
352
+ apps/my-game.dench.app/
353
+ .dench.yaml
354
+ index.html
355
+ sketch.js
356
+ assets/ # sprites, sounds, fonts
357
+ ```
358
+
359
+ **`.dench.yaml`:**
360
+ ```yaml
361
+ name: "My Game"
362
+ description: "A fun 2D game built with p5.js"
363
+ icon: "gamepad-2"
364
+ version: "1.0.0"
365
+ entry: "index.html"
366
+ runtime: "static"
367
+ ```
368
+
369
+ No permissions needed unless the game reads/writes workspace data.
370
+
371
+ **`index.html`:**
372
+ ```html
373
+ <!DOCTYPE html>
374
+ <html lang="en">
375
+ <head>
376
+ <meta charset="UTF-8">
377
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
378
+ <title>My Game</title>
379
+ <script src="https://unpkg.com/p5@1/lib/p5.min.js"></script>
380
+ <style>
381
+ * { margin: 0; padding: 0; }
382
+ html, body { width: 100%; height: 100%; overflow: hidden; }
383
+ body { display: flex; align-items: center; justify-content: center; background: #0f0f1a; }
384
+ canvas { display: block; }
385
+ </style>
386
+ </head>
387
+ <body>
388
+ <script src="sketch.js"></script>
389
+ </body>
390
+ </html>
391
+ ```
392
+
393
+ **`sketch.js` (game loop skeleton):**
394
+ ```javascript
395
+ let isDark = true;
396
+
397
+ function setup() {
398
+ createCanvas(windowWidth, windowHeight);
399
+
400
+ // Detect theme from DenchClaw
401
+ if (window.dench) {
402
+ window.dench.app.getTheme().then(theme => {
403
+ isDark = theme === 'dark';
404
+ }).catch(() => {});
405
+ }
406
+ }
407
+
408
+ function draw() {
409
+ background(isDark ? 15 : 245);
410
+
411
+ // Game rendering goes here
412
+ }
413
+
414
+ function windowResized() {
415
+ resizeCanvas(windowWidth, windowHeight);
416
+ }
417
+ ```
418
+
419
+ ### p5.js Instance Mode (Recommended for Complex Apps)
420
+
421
+ Use instance mode to avoid global namespace pollution. This is especially important for multi-file apps:
422
+
423
+ ```javascript
424
+ const sketch = (p) => {
425
+ let isDark = true;
426
+ let player;
427
+
428
+ p.setup = () => {
429
+ p.createCanvas(p.windowWidth, p.windowHeight);
430
+ player = { x: p.width / 2, y: p.height / 2, size: 30, speed: 4 };
431
+
432
+ if (window.dench) {
433
+ window.dench.app.getTheme().then(theme => { isDark = theme === 'dark'; }).catch(() => {});
434
+ }
435
+ };
436
+
437
+ p.draw = () => {
438
+ p.background(isDark ? 15 : 245);
439
+
440
+ // Input handling
441
+ if (p.keyIsDown(p.LEFT_ARROW) || p.keyIsDown(65)) player.x -= player.speed;
442
+ if (p.keyIsDown(p.RIGHT_ARROW) || p.keyIsDown(68)) player.x += player.speed;
443
+ if (p.keyIsDown(p.UP_ARROW) || p.keyIsDown(87)) player.y -= player.speed;
444
+ if (p.keyIsDown(p.DOWN_ARROW) || p.keyIsDown(83)) player.y += player.speed;
445
+
446
+ // Keep in bounds
447
+ player.x = p.constrain(player.x, 0, p.width);
448
+ player.y = p.constrain(player.y, 0, p.height);
449
+
450
+ // Draw player
451
+ p.fill(isDark ? '#6366f1' : '#4f46e5');
452
+ p.noStroke();
453
+ p.ellipse(player.x, player.y, player.size);
454
+ };
455
+
456
+ p.windowResized = () => {
457
+ p.resizeCanvas(p.windowWidth, p.windowHeight);
458
+ };
459
+ };
460
+
461
+ new p5(sketch);
462
+ ```
463
+
464
+ ### p5.js Game Architecture Patterns
465
+
466
+ #### Game State Machine
467
+
468
+ ```javascript
469
+ const GameState = { MENU: 'menu', PLAYING: 'playing', PAUSED: 'paused', GAME_OVER: 'gameover' };
470
+ let state = GameState.MENU;
471
+ let score = 0;
472
+ let highScore = 0;
473
+
474
+ function draw() {
475
+ switch (state) {
476
+ case GameState.MENU: drawMenu(); break;
477
+ case GameState.PLAYING: drawGame(); break;
478
+ case GameState.PAUSED: drawPause(); break;
479
+ case GameState.GAME_OVER: drawGameOver(); break;
480
+ }
481
+ }
482
+
483
+ function keyPressed() {
484
+ if (state === GameState.MENU && (key === ' ' || key === 'Enter')) {
485
+ state = GameState.PLAYING;
486
+ resetGame();
487
+ } else if (state === GameState.PLAYING && key === 'Escape') {
488
+ state = GameState.PAUSED;
489
+ } else if (state === GameState.PAUSED && key === 'Escape') {
490
+ state = GameState.PLAYING;
491
+ } else if (state === GameState.GAME_OVER && (key === ' ' || key === 'Enter')) {
492
+ state = GameState.PLAYING;
493
+ resetGame();
494
+ }
495
+ }
496
+
497
+ function drawMenu() {
498
+ background(15);
499
+ fill(255);
500
+ textAlign(CENTER, CENTER);
501
+ textSize(48);
502
+ text('MY GAME', width / 2, height / 2 - 60);
503
+ textSize(18);
504
+ fill(150);
505
+ text('Press SPACE or ENTER to start', width / 2, height / 2 + 20);
506
+ if (highScore > 0) {
507
+ textSize(14);
508
+ text('High Score: ' + highScore, width / 2, height / 2 + 60);
509
+ }
510
+ }
511
+
512
+ function drawGameOver() {
513
+ background(15);
514
+ fill('#ef4444');
515
+ textAlign(CENTER, CENTER);
516
+ textSize(48);
517
+ text('GAME OVER', width / 2, height / 2 - 60);
518
+ fill(255);
519
+ textSize(24);
520
+ text('Score: ' + score, width / 2, height / 2);
521
+ textSize(16);
522
+ fill(150);
523
+ text('Press SPACE to play again', width / 2, height / 2 + 50);
524
+ }
525
+ ```
526
+
527
+ #### Sprite Management
528
+
529
+ ```javascript
530
+ class Sprite {
531
+ constructor(x, y, w, h) {
532
+ this.pos = createVector(x, y);
533
+ this.vel = createVector(0, 0);
534
+ this.w = w;
535
+ this.h = h;
536
+ this.alive = true;
537
+ }
538
+
539
+ update() {
540
+ this.pos.add(this.vel);
541
+ }
542
+
543
+ draw() {
544
+ rectMode(CENTER);
545
+ rect(this.pos.x, this.pos.y, this.w, this.h);
546
+ }
547
+
548
+ collidesWith(other) {
549
+ return (
550
+ this.pos.x - this.w / 2 < other.pos.x + other.w / 2 &&
551
+ this.pos.x + this.w / 2 > other.pos.x - other.w / 2 &&
552
+ this.pos.y - this.h / 2 < other.pos.y + other.h / 2 &&
553
+ this.pos.y + this.h / 2 > other.pos.y - other.h / 2
554
+ );
555
+ }
556
+
557
+ isOffscreen() {
558
+ return (
559
+ this.pos.x < -this.w || this.pos.x > width + this.w ||
560
+ this.pos.y < -this.h || this.pos.y > height + this.h
561
+ );
562
+ }
563
+ }
564
+ ```
565
+
566
+ #### Particle System
567
+
568
+ ```javascript
569
+ class Particle {
570
+ constructor(x, y, color) {
571
+ this.pos = createVector(x, y);
572
+ this.vel = p5.Vector.random2D().mult(random(1, 5));
573
+ this.acc = createVector(0, 0.1);
574
+ this.color = color;
575
+ this.alpha = 255;
576
+ this.size = random(3, 8);
577
+ this.life = 1.0;
578
+ this.decay = random(0.01, 0.04);
579
+ }
580
+
581
+ update() {
582
+ this.vel.add(this.acc);
583
+ this.pos.add(this.vel);
584
+ this.life -= this.decay;
585
+ this.alpha = this.life * 255;
586
+ }
587
+
588
+ draw() {
589
+ noStroke();
590
+ fill(red(this.color), green(this.color), blue(this.color), this.alpha);
591
+ ellipse(this.pos.x, this.pos.y, this.size);
592
+ }
593
+
594
+ isDead() {
595
+ return this.life <= 0;
596
+ }
597
+ }
598
+
599
+ let particles = [];
600
+
601
+ function spawnExplosion(x, y, col, count = 30) {
602
+ for (let i = 0; i < count; i++) {
603
+ particles.push(new Particle(x, y, col));
604
+ }
605
+ }
606
+
607
+ function updateParticles() {
608
+ for (let i = particles.length - 1; i >= 0; i--) {
609
+ particles[i].update();
610
+ particles[i].draw();
611
+ if (particles[i].isDead()) particles.splice(i, 1);
612
+ }
613
+ }
614
+ ```
615
+
616
+ #### Camera / Scrolling
617
+
618
+ ```javascript
619
+ let camera = { x: 0, y: 0 };
620
+
621
+ function draw() {
622
+ background(15);
623
+
624
+ // Follow player
625
+ camera.x = lerp(camera.x, player.x - width / 2, 0.1);
626
+ camera.y = lerp(camera.y, player.y - height / 2, 0.1);
627
+
628
+ push();
629
+ translate(-camera.x, -camera.y);
630
+
631
+ // Draw world (in world coordinates)
632
+ drawWorld();
633
+ drawPlayer();
634
+ drawEnemies();
635
+
636
+ pop();
637
+
638
+ // Draw HUD (in screen coordinates)
639
+ drawHUD();
640
+ }
641
+ ```
642
+
643
+ #### Tilemap Rendering
644
+
645
+ ```javascript
646
+ const TILE_SIZE = 32;
647
+ const tilemap = [
648
+ [1, 1, 1, 1, 1, 1, 1, 1],
649
+ [1, 0, 0, 0, 0, 0, 0, 1],
650
+ [1, 0, 0, 2, 0, 0, 0, 1],
651
+ [1, 0, 0, 0, 0, 3, 0, 1],
652
+ [1, 1, 1, 1, 1, 1, 1, 1],
653
+ ];
654
+
655
+ const TILE_COLORS = {
656
+ 0: null, // empty
657
+ 1: '#4a4a6a', // wall
658
+ 2: '#22c55e', // item
659
+ 3: '#ef4444', // enemy
660
+ };
661
+
662
+ function drawTilemap() {
663
+ for (let row = 0; row < tilemap.length; row++) {
664
+ for (let col = 0; col < tilemap[row].length; col++) {
665
+ const tile = tilemap[row][col];
666
+ if (TILE_COLORS[tile]) {
667
+ fill(TILE_COLORS[tile]);
668
+ noStroke();
669
+ rect(col * TILE_SIZE, row * TILE_SIZE, TILE_SIZE, TILE_SIZE);
670
+ }
671
+ }
672
+ }
673
+ }
674
+ ```
675
+
676
+ #### Sound Effects (using p5.sound or Howler.js)
677
+
678
+ For sound, prefer Howler.js since p5.sound adds significant bundle size:
679
+
680
+ ```html
681
+ <script src="https://unpkg.com/howler@2/dist/howler.min.js"></script>
682
+ ```
683
+
684
+ ```javascript
685
+ const sounds = {
686
+ jump: new Howl({ src: ['assets/jump.wav'], volume: 0.5 }),
687
+ hit: new Howl({ src: ['assets/hit.wav'], volume: 0.7 }),
688
+ coin: new Howl({ src: ['assets/coin.wav'], volume: 0.4 }),
689
+ music: new Howl({ src: ['assets/music.mp3'], loop: true, volume: 0.3 }),
690
+ };
691
+ ```
692
+
693
+ If no sound assets are available, generate simple audio with Tone.js or the Web Audio API:
694
+
695
+ ```javascript
696
+ function playBeep(freq = 440, duration = 0.1) {
697
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
698
+ const osc = ctx.createOscillator();
699
+ const gain = ctx.createGain();
700
+ osc.connect(gain);
701
+ gain.connect(ctx.destination);
702
+ osc.frequency.value = freq;
703
+ gain.gain.setValueAtTime(0.3, ctx.currentTime);
704
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
705
+ osc.start();
706
+ osc.stop(ctx.currentTime + duration);
707
+ }
708
+ ```
709
+
710
+ ### p5.js with Physics (Matter.js)
711
+
712
+ For games needing realistic 2D physics (platformers, ragdoll, pinball):
713
+
714
+ ```html
715
+ <script src="https://unpkg.com/p5@1/lib/p5.min.js"></script>
716
+ <script src="https://unpkg.com/matter-js@0.20/build/matter.min.js"></script>
717
+ ```
718
+
719
+ ```javascript
720
+ const { Engine, World, Bodies, Body, Events } = Matter;
721
+
722
+ let engine, world;
723
+ let ground, player;
724
+
725
+ function setup() {
726
+ createCanvas(windowWidth, windowHeight);
727
+ engine = Engine.create();
728
+ world = engine.world;
729
+
730
+ ground = Bodies.rectangle(width / 2, height - 20, width, 40, { isStatic: true });
731
+ player = Bodies.circle(width / 2, height / 2, 20, { restitution: 0.5 });
732
+
733
+ World.add(world, [ground, player]);
734
+ }
735
+
736
+ function draw() {
737
+ Engine.update(engine);
738
+ background(15);
739
+
740
+ // Draw ground
741
+ fill('#4a4a6a');
742
+ rectMode(CENTER);
743
+ rect(ground.position.x, ground.position.y, width, 40);
744
+
745
+ // Draw player
746
+ fill('#6366f1');
747
+ ellipse(player.position.x, player.position.y, 40);
748
+ }
749
+
750
+ function keyPressed() {
751
+ if (key === ' ') {
752
+ Body.applyForce(player, player.position, { x: 0, y: -0.05 });
753
+ }
754
+ }
755
+ ```
756
+
757
+ ### p5.js Responsive Canvas
758
+
759
+ Always handle window resizing and use the full viewport:
760
+
761
+ ```javascript
762
+ function setup() {
763
+ createCanvas(windowWidth, windowHeight);
764
+ }
765
+
766
+ function windowResized() {
767
+ resizeCanvas(windowWidth, windowHeight);
768
+ }
769
+ ```
770
+
771
+ For fixed-aspect-ratio games (e.g., retro pixel games), scale the canvas:
772
+
773
+ ```javascript
774
+ const GAME_W = 320;
775
+ const GAME_H = 240;
776
+ let scaleFactor;
777
+
778
+ function setup() {
779
+ createCanvas(windowWidth, windowHeight);
780
+ pixelDensity(1);
781
+ noSmooth();
782
+ calcScale();
783
+ }
784
+
785
+ function calcScale() {
786
+ scaleFactor = min(windowWidth / GAME_W, windowHeight / GAME_H);
787
+ }
788
+
789
+ function draw() {
790
+ background(0);
791
+ push();
792
+ translate((width - GAME_W * scaleFactor) / 2, (height - GAME_H * scaleFactor) / 2);
793
+ scale(scaleFactor);
794
+
795
+ // All game drawing at GAME_W x GAME_H logical resolution
796
+ drawGameWorld();
797
+
798
+ pop();
799
+ }
800
+
801
+ function windowResized() {
802
+ resizeCanvas(windowWidth, windowHeight);
803
+ calcScale();
804
+ }
805
+ ```
806
+
807
+ ### Touch / Mobile Input for p5.js Games
808
+
809
+ ```javascript
810
+ let touchActive = false;
811
+ let touchX = 0, touchY = 0;
812
+
813
+ function touchStarted() {
814
+ touchActive = true;
815
+ touchX = mouseX;
816
+ touchY = mouseY;
817
+ return false; // prevent default
818
+ }
819
+
820
+ function touchMoved() {
821
+ touchX = mouseX;
822
+ touchY = mouseY;
823
+ return false;
824
+ }
825
+
826
+ function touchEnded() {
827
+ touchActive = false;
828
+ return false;
829
+ }
830
+
831
+ // Unified input: works for both mouse and touch
832
+ function getInputX() { return mouseX; }
833
+ function getInputY() { return mouseY; }
834
+ function isInputActive() { return mouseIsPressed || touchActive; }
835
+ ```
836
+
837
+ ### p5.js High Score Persistence with DuckDB
838
+
839
+ If the game has a `database` permission, persist high scores:
840
+
841
+ ```javascript
842
+ async function loadHighScore() {
843
+ try {
844
+ await window.dench.db.execute(`
845
+ CREATE TABLE IF NOT EXISTS game_scores (
846
+ game TEXT, score INTEGER, played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
847
+ )
848
+ `);
849
+ const result = await window.dench.db.query(
850
+ `SELECT MAX(score) as high_score FROM game_scores WHERE game = 'my-game'`
851
+ );
852
+ return result.rows?.[0]?.high_score || 0;
853
+ } catch { return 0; }
854
+ }
855
+
856
+ async function saveScore(score) {
857
+ try {
858
+ await window.dench.db.execute(
859
+ `INSERT INTO game_scores (game, score) VALUES ('my-game', ${score})`
860
+ );
861
+ } catch {}
862
+ }
863
+ ```
864
+
865
+ ---
866
+
867
+ ## 3D Games & Experiences with Three.js
868
+
869
+ **Always use Three.js for 3D games, visualizations, and interactive 3D experiences.** Three.js is the default choice for anything 3D.
870
+
871
+ ### When to Use Three.js
872
+
873
+ - 3D games (first-person, third-person, flying, racing)
874
+ - 3D product viewers and configurators
875
+ - Terrain and world visualization
876
+ - 3D data visualization (3D scatter plots, network graphs)
877
+ - Architectural walkthroughs
878
+ - Generative 3D art
879
+ - Physics-based 3D simulations
880
+
881
+ ### Three.js App Template
882
+
883
+ ```
884
+ apps/my-3d-app.dench.app/
885
+ .dench.yaml
886
+ index.html
887
+ app.js # Main Three.js module
888
+ assets/
889
+ model.glb # 3D models (optional)
890
+ texture.jpg # Textures (optional)
891
+ ```
892
+
893
+ **`.dench.yaml`:**
894
+ ```yaml
895
+ name: "3D World"
896
+ description: "An interactive 3D experience"
897
+ icon: "box"
898
+ version: "1.0.0"
899
+ entry: "index.html"
900
+ runtime: "static"
901
+ ```
902
+
903
+ **`index.html`:**
904
+ ```html
905
+ <!DOCTYPE html>
906
+ <html lang="en">
907
+ <head>
908
+ <meta charset="UTF-8">
909
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
910
+ <title>3D World</title>
911
+ <script type="importmap">
912
+ {
913
+ "imports": {
914
+ "three": "https://unpkg.com/three@0.170/build/three.module.js",
915
+ "three/addons/": "https://unpkg.com/three@0.170/examples/jsm/"
916
+ }
917
+ }
918
+ </script>
919
+ <style>
920
+ * { margin: 0; padding: 0; }
921
+ html, body { width: 100%; height: 100%; overflow: hidden; }
922
+ canvas { display: block; }
923
+ #loading {
924
+ position: fixed; inset: 0; display: flex;
925
+ align-items: center; justify-content: center;
926
+ background: #0f0f1a; color: #e8e8f0;
927
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
928
+ font-size: 18px; z-index: 10;
929
+ transition: opacity 0.5s;
930
+ }
931
+ #loading.hidden { opacity: 0; pointer-events: none; }
932
+ </style>
933
+ </head>
934
+ <body>
935
+ <div id="loading">Loading...</div>
936
+ <script type="module" src="app.js"></script>
937
+ </body>
938
+ </html>
939
+ ```
940
+
941
+ **`app.js` (Three.js module skeleton):**
942
+ ```javascript
943
+ import * as THREE from 'three';
944
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
945
+
946
+ // --- Scene setup ---
947
+ const scene = new THREE.Scene();
948
+ scene.background = new THREE.Color(0x0f0f1a);
949
+ scene.fog = new THREE.Fog(0x0f0f1a, 50, 200);
950
+
951
+ const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
952
+ camera.position.set(0, 5, 10);
953
+
954
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
955
+ renderer.setSize(window.innerWidth, window.innerHeight);
956
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
957
+ renderer.shadowMap.enabled = true;
958
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
959
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
960
+ renderer.toneMappingExposure = 1.0;
961
+ document.body.appendChild(renderer.domElement);
962
+
963
+ // --- Controls ---
964
+ const controls = new OrbitControls(camera, renderer.domElement);
965
+ controls.enableDamping = true;
966
+ controls.dampingFactor = 0.05;
967
+ controls.maxPolarAngle = Math.PI / 2;
968
+
969
+ // --- Lighting ---
970
+ const ambientLight = new THREE.AmbientLight(0x404060, 0.5);
971
+ scene.add(ambientLight);
972
+
973
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
974
+ directionalLight.position.set(10, 20, 10);
975
+ directionalLight.castShadow = true;
976
+ directionalLight.shadow.mapSize.set(2048, 2048);
977
+ directionalLight.shadow.camera.near = 0.5;
978
+ directionalLight.shadow.camera.far = 100;
979
+ directionalLight.shadow.camera.left = -30;
980
+ directionalLight.shadow.camera.right = 30;
981
+ directionalLight.shadow.camera.top = 30;
982
+ directionalLight.shadow.camera.bottom = -30;
983
+ scene.add(directionalLight);
984
+
985
+ // --- Ground ---
986
+ const groundGeo = new THREE.PlaneGeometry(200, 200);
987
+ const groundMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.8 });
988
+ const ground = new THREE.Mesh(groundGeo, groundMat);
989
+ ground.rotation.x = -Math.PI / 2;
990
+ ground.receiveShadow = true;
991
+ scene.add(ground);
992
+
993
+ // --- Objects ---
994
+ const geometry = new THREE.BoxGeometry(2, 2, 2);
995
+ const material = new THREE.MeshStandardMaterial({
996
+ color: 0x6366f1,
997
+ roughness: 0.3,
998
+ metalness: 0.5,
999
+ });
1000
+ const cube = new THREE.Mesh(geometry, material);
1001
+ cube.position.y = 1;
1002
+ cube.castShadow = true;
1003
+ scene.add(cube);
1004
+
1005
+ // --- Theme ---
1006
+ if (window.dench) {
1007
+ window.dench.app.getTheme().then(theme => {
1008
+ if (theme === 'light') {
1009
+ scene.background = new THREE.Color(0xf0f0f5);
1010
+ scene.fog = new THREE.Fog(0xf0f0f5, 50, 200);
1011
+ groundMat.color.set(0xe8e8f0);
1012
+ }
1013
+ }).catch(() => {});
1014
+ }
1015
+
1016
+ // --- Hide loading screen ---
1017
+ document.getElementById('loading')?.classList.add('hidden');
1018
+
1019
+ // --- Animation loop ---
1020
+ const clock = new THREE.Clock();
1021
+
1022
+ function animate() {
1023
+ requestAnimationFrame(animate);
1024
+ const dt = clock.getDelta();
1025
+ const elapsed = clock.getElapsedTime();
1026
+
1027
+ cube.rotation.y = elapsed * 0.5;
1028
+ cube.position.y = 1 + Math.sin(elapsed) * 0.5;
1029
+
1030
+ controls.update();
1031
+ renderer.render(scene, camera);
1032
+ }
1033
+
1034
+ animate();
1035
+
1036
+ // --- Resize ---
1037
+ window.addEventListener('resize', () => {
1038
+ camera.aspect = window.innerWidth / window.innerHeight;
1039
+ camera.updateProjectionMatrix();
1040
+ renderer.setSize(window.innerWidth, window.innerHeight);
1041
+ });
1042
+ ```
1043
+
1044
+ ### Three.js Common Addons
1045
+
1046
+ Load additional Three.js modules as needed via the import map:
1047
+
1048
+ ```javascript
1049
+ // First-person controls
1050
+ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
1051
+
1052
+ // GLTF model loading
1053
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
1054
+
1055
+ // Post-processing
1056
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
1057
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
1058
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
1059
+
1060
+ // Environment maps
1061
+ import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
1062
+
1063
+ // Text
1064
+ import { FontLoader } from 'three/addons/loaders/FontLoader.js';
1065
+ import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
1066
+
1067
+ // Sky
1068
+ import { Sky } from 'three/addons/objects/Sky.js';
1069
+
1070
+ // Water
1071
+ import { Water } from 'three/addons/objects/Water.js';
1072
+
1073
+ // Physics integration (use cannon-es via CDN)
1074
+ // Add to importmap: "cannon-es": "https://unpkg.com/cannon-es@0.20/dist/cannon-es.js"
1075
+ import * as CANNON from 'cannon-es';
1076
+ ```
1077
+
1078
+ ### Three.js First-Person Game Pattern
1079
+
1080
+ ```javascript
1081
+ import * as THREE from 'three';
1082
+ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
1083
+
1084
+ const scene = new THREE.Scene();
1085
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
1086
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
1087
+ renderer.setSize(window.innerWidth, window.innerHeight);
1088
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
1089
+ document.body.appendChild(renderer.domElement);
1090
+
1091
+ const controls = new PointerLockControls(camera, document.body);
1092
+
1093
+ // Click to enter pointer lock
1094
+ document.addEventListener('click', () => {
1095
+ if (!controls.isLocked) controls.lock();
1096
+ });
1097
+
1098
+ // Movement state
1099
+ const velocity = new THREE.Vector3();
1100
+ const direction = new THREE.Vector3();
1101
+ const keys = { forward: false, backward: false, left: false, right: false, jump: false };
1102
+
1103
+ document.addEventListener('keydown', (e) => {
1104
+ switch (e.code) {
1105
+ case 'KeyW': case 'ArrowUp': keys.forward = true; break;
1106
+ case 'KeyS': case 'ArrowDown': keys.backward = true; break;
1107
+ case 'KeyA': case 'ArrowLeft': keys.left = true; break;
1108
+ case 'KeyD': case 'ArrowRight': keys.right = true; break;
1109
+ case 'Space': keys.jump = true; break;
1110
+ }
1111
+ });
1112
+
1113
+ document.addEventListener('keyup', (e) => {
1114
+ switch (e.code) {
1115
+ case 'KeyW': case 'ArrowUp': keys.forward = false; break;
1116
+ case 'KeyS': case 'ArrowDown': keys.backward = false; break;
1117
+ case 'KeyA': case 'ArrowLeft': keys.left = false; break;
1118
+ case 'KeyD': case 'ArrowRight': keys.right = false; break;
1119
+ case 'Space': keys.jump = false; break;
1120
+ }
1121
+ });
1122
+
1123
+ let onGround = true;
1124
+ const MOVE_SPEED = 50;
1125
+ const JUMP_FORCE = 12;
1126
+ const GRAVITY = 30;
1127
+
1128
+ const clock = new THREE.Clock();
1129
+
1130
+ function animate() {
1131
+ requestAnimationFrame(animate);
1132
+ const dt = Math.min(clock.getDelta(), 0.1);
1133
+
1134
+ if (controls.isLocked) {
1135
+ // Apply friction
1136
+ velocity.x -= velocity.x * 10.0 * dt;
1137
+ velocity.z -= velocity.z * 10.0 * dt;
1138
+ velocity.y -= GRAVITY * dt;
1139
+
1140
+ direction.z = Number(keys.forward) - Number(keys.backward);
1141
+ direction.x = Number(keys.right) - Number(keys.left);
1142
+ direction.normalize();
1143
+
1144
+ if (keys.forward || keys.backward) velocity.z -= direction.z * MOVE_SPEED * dt;
1145
+ if (keys.left || keys.right) velocity.x -= direction.x * MOVE_SPEED * dt;
1146
+ if (keys.jump && onGround) { velocity.y = JUMP_FORCE; onGround = false; }
1147
+
1148
+ controls.moveRight(-velocity.x * dt);
1149
+ controls.moveForward(-velocity.z * dt);
1150
+ camera.position.y += velocity.y * dt;
1151
+
1152
+ if (camera.position.y < 1.7) {
1153
+ velocity.y = 0;
1154
+ camera.position.y = 1.7;
1155
+ onGround = true;
1156
+ }
1157
+ }
1158
+
1159
+ renderer.render(scene, camera);
1160
+ }
1161
+
1162
+ animate();
1163
+ ```
1164
+
1165
+ ### Three.js GLTF Model Loading
1166
+
1167
+ ```javascript
1168
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
1169
+ import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
1170
+
1171
+ const loader = new GLTFLoader();
1172
+ const dracoLoader = new DRACOLoader();
1173
+ dracoLoader.setDecoderPath('https://unpkg.com/three@0.170/examples/jsm/libs/draco/');
1174
+ loader.setDRACOLoader(dracoLoader);
1175
+
1176
+ // Load a model from the app's assets folder
1177
+ loader.load('assets/model.glb', (gltf) => {
1178
+ const model = gltf.scene;
1179
+ model.traverse((child) => {
1180
+ if (child.isMesh) {
1181
+ child.castShadow = true;
1182
+ child.receiveShadow = true;
1183
+ }
1184
+ });
1185
+ model.scale.setScalar(1);
1186
+ scene.add(model);
1187
+
1188
+ // If the model has animations
1189
+ if (gltf.animations.length > 0) {
1190
+ const mixer = new THREE.AnimationMixer(model);
1191
+ const action = mixer.clipAction(gltf.animations[0]);
1192
+ action.play();
1193
+ // In animate loop: mixer.update(dt);
1194
+ }
1195
+ }, undefined, (error) => {
1196
+ console.error('Model load error:', error);
1197
+ });
1198
+ ```
1199
+
1200
+ ### Three.js Post-Processing (Bloom, etc.)
1201
+
1202
+ ```javascript
1203
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
1204
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
1205
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
1206
+
1207
+ const composer = new EffectComposer(renderer);
1208
+ composer.addPass(new RenderPass(scene, camera));
1209
+
1210
+ const bloomPass = new UnrealBloomPass(
1211
+ new THREE.Vector2(window.innerWidth, window.innerHeight),
1212
+ 0.5, // strength
1213
+ 0.4, // radius
1214
+ 0.85 // threshold
1215
+ );
1216
+ composer.addPass(bloomPass);
1217
+
1218
+ // In animate loop, replace renderer.render(scene, camera) with:
1219
+ // composer.render();
1220
+
1221
+ // On resize, also update:
1222
+ // composer.setSize(window.innerWidth, window.innerHeight);
1223
+ ```
1224
+
1225
+ ### Three.js Procedural Terrain
1226
+
1227
+ ```javascript
1228
+ function createTerrain(width, depth, resolution) {
1229
+ const geometry = new THREE.PlaneGeometry(width, depth, resolution, resolution);
1230
+ const vertices = geometry.attributes.position.array;
1231
+
1232
+ for (let i = 0; i < vertices.length; i += 3) {
1233
+ const x = vertices[i];
1234
+ const z = vertices[i + 1];
1235
+ vertices[i + 2] = noise(x * 0.02, z * 0.02) * 15;
1236
+ }
1237
+
1238
+ geometry.computeVertexNormals();
1239
+
1240
+ const material = new THREE.MeshStandardMaterial({
1241
+ color: 0x3a7d44,
1242
+ roughness: 0.9,
1243
+ flatShading: true,
1244
+ });
1245
+
1246
+ const mesh = new THREE.Mesh(geometry, material);
1247
+ mesh.rotation.x = -Math.PI / 2;
1248
+ mesh.receiveShadow = true;
1249
+ return mesh;
1250
+ }
1251
+
1252
+ // Simple noise function (for procedural generation without dependencies)
1253
+ function noise(x, y) {
1254
+ const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
1255
+ return n - Math.floor(n);
1256
+ }
1257
+ ```
1258
+
1259
+ ### Three.js HUD / UI Overlay
1260
+
1261
+ Since Three.js renders to a canvas, use HTML overlays for UI:
1262
+
1263
+ ```html
1264
+ <div id="hud" style="
1265
+ position: fixed; top: 0; left: 0; right: 0;
1266
+ padding: 16px; pointer-events: none;
1267
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
1268
+ color: white; z-index: 5;
1269
+ ">
1270
+ <div id="score" style="font-size: 24px; font-weight: 700;"></div>
1271
+ <div id="health-bar" style="
1272
+ width: 200px; height: 8px; border-radius: 4px;
1273
+ background: rgba(255,255,255,0.2); margin-top: 8px;
1274
+ ">
1275
+ <div id="health-fill" style="
1276
+ width: 100%; height: 100%; border-radius: 4px;
1277
+ background: #22c55e; transition: width 0.3s;
1278
+ "></div>
1279
+ </div>
1280
+ </div>
1281
+ ```
1282
+
1283
+ ```javascript
1284
+ function updateHUD(score, health) {
1285
+ document.getElementById('score').textContent = `Score: ${score}`;
1286
+ document.getElementById('health-fill').style.width = `${health}%`;
1287
+ document.getElementById('health-fill').style.background =
1288
+ health > 50 ? '#22c55e' : health > 25 ? '#f59e0b' : '#ef4444';
1289
+ }
1290
+ ```
1291
+
1292
+ ---
1293
+
1294
+ ## Data Dashboards & Visualization
1295
+
1296
+ For data-heavy apps that query the workspace DuckDB, use Chart.js, D3.js, or plain HTML/CSS.
1297
+
1298
+ ### Chart.js Dashboard
1299
+
1300
+ ```html
1301
+ <script src="https://unpkg.com/chart.js@4/dist/chart.umd.min.js"></script>
1302
+ ```
1303
+
1304
+ ```javascript
1305
+ async function renderChart() {
1306
+ const result = await window.dench.db.query(
1307
+ "SELECT name, entry_count FROM objects ORDER BY entry_count DESC"
1308
+ );
1309
+
1310
+ const ctx = document.getElementById('myChart').getContext('2d');
1311
+ new Chart(ctx, {
1312
+ type: 'bar',
1313
+ data: {
1314
+ labels: result.rows.map(r => r.name),
1315
+ datasets: [{
1316
+ label: 'Entries',
1317
+ data: result.rows.map(r => r.entry_count),
1318
+ backgroundColor: '#6366f180',
1319
+ borderColor: '#6366f1',
1320
+ borderWidth: 1,
1321
+ borderRadius: 6,
1322
+ }]
1323
+ },
1324
+ options: {
1325
+ responsive: true,
1326
+ maintainAspectRatio: false,
1327
+ plugins: { legend: { display: false } },
1328
+ scales: {
1329
+ y: { beginAtZero: true, grid: { color: '#2a2a4530' } },
1330
+ x: { grid: { display: false } },
1331
+ }
1332
+ }
1333
+ });
1334
+ }
1335
+ ```
1336
+
1337
+ ### D3.js Visualization
1338
+
1339
+ ```html
1340
+ <script src="https://unpkg.com/d3@7/dist/d3.min.js"></script>
1341
+ ```
1342
+
1343
+ ```javascript
1344
+ async function renderViz() {
1345
+ const result = await window.dench.db.query("SELECT * FROM pivot_people LIMIT 100");
1346
+ const data = result.rows;
1347
+
1348
+ const margin = { top: 20, right: 20, bottom: 40, left: 60 };
1349
+ const width = window.innerWidth - margin.left - margin.right;
1350
+ const height = 400 - margin.top - margin.bottom;
1351
+
1352
+ const svg = d3.select('#chart')
1353
+ .append('svg')
1354
+ .attr('width', width + margin.left + margin.right)
1355
+ .attr('height', height + margin.top + margin.bottom)
1356
+ .append('g')
1357
+ .attr('transform', `translate(${margin.left},${margin.top})`);
1358
+
1359
+ // Build scales, axes, bindings as needed
1360
+ }
1361
+ ```
1362
+
1363
+ ### CSS-Only Stat Cards (No Library Needed)
1364
+
1365
+ For simple metric displays, plain HTML/CSS is often better than a charting library:
1366
+
1367
+ ```html
1368
+ <div class="stats-grid">
1369
+ <div class="stat-card">
1370
+ <div class="stat-label">Total Records</div>
1371
+ <div class="stat-value" id="total">—</div>
1372
+ <div class="stat-change positive">+12% this week</div>
1373
+ </div>
1374
+ </div>
1375
+ ```
1376
+
1377
+ ```css
1378
+ .stats-grid {
1379
+ display: grid;
1380
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1381
+ gap: 16px;
1382
+ padding: 24px;
1383
+ }
1384
+
1385
+ .stat-card {
1386
+ padding: 20px;
1387
+ border-radius: 12px;
1388
+ background: var(--app-surface);
1389
+ border: 1px solid var(--app-border);
1390
+ }
1391
+
1392
+ .stat-label {
1393
+ font-size: 13px;
1394
+ color: var(--app-text-muted);
1395
+ margin-bottom: 8px;
1396
+ }
1397
+
1398
+ .stat-value {
1399
+ font-size: 36px;
1400
+ font-weight: 700;
1401
+ font-variant-numeric: tabular-nums;
1402
+ }
1403
+
1404
+ .stat-change {
1405
+ font-size: 12px;
1406
+ margin-top: 4px;
1407
+ }
1408
+
1409
+ .stat-change.positive { color: var(--app-success); }
1410
+ .stat-change.negative { color: var(--app-error); }
1411
+ ```
1412
+
1413
+ ---
1414
+
1415
+ ## Interactive Tools & Utilities
1416
+
1417
+ ### Form-Based Tools
1418
+
1419
+ For tools that collect input and process it:
1420
+
1421
+ ```html
1422
+ <form id="tool-form">
1423
+ <div class="field">
1424
+ <label for="input">Input</label>
1425
+ <textarea id="input" rows="6" placeholder="Paste your data here..."></textarea>
1426
+ </div>
1427
+ <button type="submit">Process</button>
1428
+ <div id="output" class="output-box"></div>
1429
+ </form>
1430
+ ```
1431
+
1432
+ ```css
1433
+ .field { margin-bottom: 16px; }
1434
+ .field label {
1435
+ display: block;
1436
+ font-size: 13px;
1437
+ font-weight: 500;
1438
+ color: var(--app-text-muted);
1439
+ margin-bottom: 6px;
1440
+ }
1441
+ .field textarea, .field input, .field select {
1442
+ width: 100%;
1443
+ padding: 10px 12px;
1444
+ border-radius: 8px;
1445
+ border: 1px solid var(--app-border);
1446
+ background: var(--app-surface);
1447
+ color: var(--app-text);
1448
+ font-size: 14px;
1449
+ font-family: inherit;
1450
+ resize: vertical;
1451
+ }
1452
+ .field textarea:focus, .field input:focus {
1453
+ outline: none;
1454
+ border-color: var(--app-accent);
1455
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-accent) 20%, transparent);
1456
+ }
1457
+ button[type="submit"] {
1458
+ padding: 10px 20px;
1459
+ border: none;
1460
+ border-radius: 8px;
1461
+ background: var(--app-accent);
1462
+ color: white;
1463
+ font-size: 14px;
1464
+ font-weight: 500;
1465
+ cursor: pointer;
1466
+ transition: background 0.15s;
1467
+ }
1468
+ button[type="submit"]:hover { background: var(--app-accent-hover); }
1469
+ .output-box {
1470
+ margin-top: 16px;
1471
+ padding: 16px;
1472
+ border-radius: 8px;
1473
+ background: var(--app-surface);
1474
+ border: 1px solid var(--app-border);
1475
+ font-family: 'SF Mono', 'Fira Code', monospace;
1476
+ font-size: 13px;
1477
+ white-space: pre-wrap;
1478
+ max-height: 400px;
1479
+ overflow: auto;
1480
+ }
1481
+ ```
1482
+
1483
+ ### Kanban / Drag-and-Drop
1484
+
1485
+ For sortable/draggable interfaces, use the native HTML Drag and Drop API or load SortableJS:
1486
+
1487
+ ```html
1488
+ <script src="https://unpkg.com/sortablejs@1/Sortable.min.js"></script>
1489
+ ```
1490
+
1491
+ ```javascript
1492
+ document.querySelectorAll('.kanban-column').forEach(col => {
1493
+ Sortable.create(col, {
1494
+ group: 'tasks',
1495
+ animation: 150,
1496
+ ghostClass: 'drag-ghost',
1497
+ onEnd: (evt) => {
1498
+ // Persist order change via DuckDB if needed
1499
+ },
1500
+ });
1501
+ });
1502
+ ```
1503
+
1504
+ ---
1505
+
1506
+ ## Multi-File App Organization
1507
+
1508
+ For complex apps, split code across multiple files:
1509
+
1510
+ ```
1511
+ apps/complex-app.dench.app/
1512
+ .dench.yaml
1513
+ index.html
1514
+ css/
1515
+ main.css
1516
+ components.css
1517
+ js/
1518
+ app.js # Entry point
1519
+ game.js # Game logic
1520
+ renderer.js # Rendering
1521
+ ui.js # UI overlays
1522
+ utils.js # Helpers
1523
+ assets/
1524
+ sprites/
1525
+ sounds/
1526
+ models/
1527
+ ```
1528
+
1529
+ ### Using ES Modules for Multi-File JS
1530
+
1531
+ ```html
1532
+ <script type="module" src="js/app.js"></script>
1533
+ ```
1534
+
1535
+ ```javascript
1536
+ // js/app.js
1537
+ import { Game } from './game.js';
1538
+ import { Renderer } from './renderer.js';
1539
+ import { UI } from './ui.js';
1540
+
1541
+ const game = new Game();
1542
+ const renderer = new Renderer(game);
1543
+ const ui = new UI(game);
1544
+
1545
+ async function init() {
1546
+ if (window.dench) {
1547
+ const theme = await window.dench.app.getTheme();
1548
+ renderer.setTheme(theme);
1549
+ }
1550
+ game.start();
1551
+ }
1552
+
1553
+ init();
1554
+ ```
1555
+
1556
+ ```javascript
1557
+ // js/game.js
1558
+ export class Game {
1559
+ constructor() {
1560
+ this.state = 'menu';
1561
+ this.score = 0;
1562
+ this.entities = [];
1563
+ }
1564
+
1565
+ start() { this.state = 'playing'; this.loop(); }
1566
+ loop() {
1567
+ this.update();
1568
+ requestAnimationFrame(() => this.loop());
1569
+ }
1570
+ update() { /* game logic */ }
1571
+ }
1572
+ ```
1573
+
1574
+ Relative imports (`./game.js`) work because all files are served from the same `/api/apps/serve/` base path.
1575
+
1576
+ ---
1577
+
1578
+ ## Asset Management
1579
+
1580
+ ### Referencing Assets
1581
+
1582
+ All asset paths are relative to the `.dench.app` folder root:
1583
+
1584
+ ```javascript
1585
+ // In p5.js
1586
+ let img;
1587
+ function preload() {
1588
+ img = loadImage('assets/player.png');
1589
+ }
1590
+
1591
+ // In Three.js (module)
1592
+ const texture = new THREE.TextureLoader().load('assets/texture.jpg');
1593
+
1594
+ // In HTML
1595
+ // <img src="assets/logo.png" />
1596
+ // <audio src="assets/music.mp3"></audio>
1597
+ ```
1598
+
1599
+ ### Supported MIME Types
1600
+
1601
+ The file server recognizes these extensions automatically:
1602
+
1603
+ | Extension | MIME Type |
1604
+ |-----------|-----------|
1605
+ | `.html`, `.htm` | `text/html` |
1606
+ | `.css` | `text/css` |
1607
+ | `.js`, `.mjs` | `application/javascript` |
1608
+ | `.json` | `application/json` |
1609
+ | `.png` | `image/png` |
1610
+ | `.jpg`, `.jpeg` | `image/jpeg` |
1611
+ | `.gif` | `image/gif` |
1612
+ | `.svg` | `image/svg+xml` |
1613
+ | `.webp` | `image/webp` |
1614
+ | `.woff`, `.woff2` | `font/woff`, `font/woff2` |
1615
+ | `.ttf`, `.otf` | `font/ttf`, `font/otf` |
1616
+ | `.wasm` | `application/wasm` |
1617
+ | `.mp3`, `.wav`, `.ogg` | Served as `application/octet-stream` (works fine for `<audio>` and Howler) |
1618
+
1619
+ ### Generating Assets Inline
1620
+
1621
+ For games without pre-made art, generate sprites and textures programmatically:
1622
+
1623
+ ```javascript
1624
+ // p5.js: Create a sprite at runtime
1625
+ function createPlayerSprite(size) {
1626
+ const g = createGraphics(size, size);
1627
+ g.noStroke();
1628
+ g.fill('#6366f1');
1629
+ g.ellipse(size / 2, size / 2, size * 0.8);
1630
+ g.fill('#818cf8');
1631
+ g.ellipse(size / 2, size / 3, size * 0.3);
1632
+ return g;
1633
+ }
1634
+
1635
+ // Three.js: Create a texture from canvas
1636
+ function createCheckerTexture(size = 256, divisions = 8) {
1637
+ const canvas = document.createElement('canvas');
1638
+ canvas.width = canvas.height = size;
1639
+ const ctx = canvas.getContext('2d');
1640
+ const cellSize = size / divisions;
1641
+ for (let y = 0; y < divisions; y++) {
1642
+ for (let x = 0; x < divisions; x++) {
1643
+ ctx.fillStyle = (x + y) % 2 === 0 ? '#ffffff' : '#cccccc';
1644
+ ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
1645
+ }
1646
+ }
1647
+ const texture = new THREE.CanvasTexture(canvas);
1648
+ texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
1649
+ return texture;
1650
+ }
1651
+ ```
1652
+
1653
+ ---
1654
+
1655
+ ## Performance & Best Practices
1656
+
1657
+ ### General
1658
+
1659
+ - **Always use `runtime: "static"`** unless explicitly asked for React/TSX/npm
1660
+ - **Request only needed permissions** — no permissions needed for pure games/tools
1661
+ - **Keep apps self-contained** — all resources within the `.dench.app` folder
1662
+ - **Use semantic HTML** and responsive design
1663
+ - **Handle errors** for all bridge API calls
1664
+ - **Apply the theme** as the very first thing on load
1665
+ - **Use `requestAnimationFrame`** for all animation loops (p5.js does this automatically)
1666
+ - **Clean up resources** in games: remove event listeners, cancel animations, dispose Three.js objects
1667
+
1668
+ ### p5.js Performance
1669
+
1670
+ - Use `pixelDensity(1)` for pixel-art or retro-style games to avoid unnecessary high-DPI rendering
1671
+ - Use `noSmooth()` for pixel-art aesthetics
1672
+ - Minimize `createGraphics()` calls — create off-screen buffers once and reuse
1673
+ - Object pool frequently-created entities (bullets, particles) instead of creating new objects each frame
1674
+ - Use `p.frameRate(60)` explicitly to cap FPS
1675
+ - For large worlds, only render entities visible on screen (frustum culling)
1676
+ - Use `p.millis()` or `p.deltaTime` for time-based movement instead of frame-based
1677
+
1678
+ ### Three.js Performance
1679
+
1680
+ - **Limit pixel ratio**: `renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))`
1681
+ - **Reuse geometries and materials** — don't create new ones per object if the shape/material is the same
1682
+ - **Dispose resources** when no longer needed: `geometry.dispose()`, `material.dispose()`, `texture.dispose()`
1683
+ - **Use `BufferGeometry`** (the default in modern Three.js)
1684
+ - **Merge static meshes** with `BufferGeometryUtils.mergeGeometries()` for large scenes
1685
+ - **Use instanced rendering** (`InstancedMesh`) for many identical objects (trees, particles)
1686
+ - **Limit shadow map resolution** on mobile
1687
+ - **Use LOD** (Level of Detail) for complex models: `THREE.LOD`
1688
+ - **Throttle physics** to a fixed timestep (e.g., 60Hz) separate from render framerate
1689
+
1690
+ ### Memory
1691
+
1692
+ - For Three.js, always clean up in a dispose function:
1693
+ ```javascript
1694
+ function dispose() {
1695
+ renderer.dispose();
1696
+ scene.traverse((obj) => {
1697
+ if (obj.geometry) obj.geometry.dispose();
1698
+ if (obj.material) {
1699
+ if (Array.isArray(obj.material)) obj.material.forEach(m => m.dispose());
1700
+ else obj.material.dispose();
1701
+ }
1702
+ });
1703
+ }
1704
+ ```
1705
+
1706
+ ---
1707
+
1708
+ ## Error Handling Patterns
1709
+
1710
+ ### Bridge API Error Handling
1711
+
1712
+ Always wrap bridge calls in try/catch:
1713
+
1714
+ ```javascript
1715
+ async function loadData() {
1716
+ try {
1717
+ const result = await window.dench.db.query("SELECT * FROM objects");
1718
+ return result.rows || [];
1719
+ } catch (err) {
1720
+ console.error('Failed to load data:', err.message);
1721
+ showError('Could not load workspace data. Check permissions.');
1722
+ return [];
1723
+ }
1724
+ }
1725
+ ```
1726
+
1727
+ ### Loading State Pattern
1728
+
1729
+ ```javascript
1730
+ function showLoading(message = 'Loading...') {
1731
+ const el = document.getElementById('loading');
1732
+ if (el) { el.textContent = message; el.style.display = 'flex'; }
1733
+ }
1734
+
1735
+ function hideLoading() {
1736
+ const el = document.getElementById('loading');
1737
+ if (el) el.style.display = 'none';
1738
+ }
1739
+
1740
+ function showError(message) {
1741
+ const el = document.getElementById('error');
1742
+ if (el) { el.textContent = message; el.style.display = 'block'; }
1743
+ }
1744
+ ```
1745
+
1746
+ ### Graceful Degradation
1747
+
1748
+ ```javascript
1749
+ async function init() {
1750
+ // Apply theme (degrade gracefully if bridge unavailable)
1751
+ try {
1752
+ const theme = await window.dench.app.getTheme();
1753
+ document.body.className = theme;
1754
+ } catch {
1755
+ document.body.className = 'dark';
1756
+ }
1757
+
1758
+ // Load data (show fallback UI if unavailable)
1759
+ try {
1760
+ const data = await window.dench.db.query("SELECT * FROM objects");
1761
+ renderDashboard(data.rows);
1762
+ } catch {
1763
+ renderEmptyState('No data available. Make sure the app has database permission.');
1764
+ }
1765
+ }
1766
+ ```
1767
+
1768
+ ---
1769
+
1770
+ ## Full Example Apps
1771
+
1772
+ ### Example 1: Arcade Game (p5.js)
1773
+
1774
+ A complete asteroid-dodge game with scoring, particles, and game states.
1775
+
1776
+ **`.dench.yaml`:**
1777
+ ```yaml
1778
+ name: "Asteroid Dodge"
1779
+ description: "Dodge the falling asteroids! Arrow keys or WASD to move."
1780
+ icon: "rocket"
1781
+ version: "1.0.0"
1782
+ entry: "index.html"
1783
+ runtime: "static"
1784
+ ```
1785
+
1786
+ **`index.html`:**
1787
+ ```html
1788
+ <!DOCTYPE html>
1789
+ <html lang="en">
1790
+ <head>
1791
+ <meta charset="UTF-8">
1792
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1793
+ <title>Asteroid Dodge</title>
1794
+ <script src="https://unpkg.com/p5@1/lib/p5.min.js"></script>
1795
+ <style>
1796
+ * { margin: 0; padding: 0; }
1797
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a1a; }
1798
+ canvas { display: block; }
1799
+ </style>
1800
+ </head>
1801
+ <body>
1802
+ <script src="game.js"></script>
1803
+ </body>
1804
+ </html>
1805
+ ```
1806
+
1807
+ **`game.js`:**
1808
+ ```javascript
1809
+ const State = { MENU: 0, PLAY: 1, OVER: 2 };
1810
+ let state = State.MENU;
1811
+ let player, asteroids, particles, stars;
1812
+ let score, highScore = 0, spawnTimer, difficulty;
1813
+
1814
+ function setup() {
1815
+ createCanvas(windowWidth, windowHeight);
1816
+ textFont('system-ui');
1817
+
1818
+ stars = Array.from({ length: 100 }, () => ({
1819
+ x: random(width), y: random(height), s: random(1, 3), b: random(100, 255)
1820
+ }));
1821
+
1822
+ if (window.dench) {
1823
+ window.dench.app.getTheme().catch(() => {});
1824
+ }
1825
+ }
1826
+
1827
+ function resetGame() {
1828
+ player = { x: width / 2, y: height - 80, size: 24, speed: 5, lives: 3, invincible: 0 };
1829
+ asteroids = [];
1830
+ particles = [];
1831
+ score = 0;
1832
+ spawnTimer = 0;
1833
+ difficulty = 1;
1834
+ }
1835
+
1836
+ function draw() {
1837
+ background(10, 10, 26);
1838
+ drawStars();
1839
+
1840
+ switch (state) {
1841
+ case State.MENU: drawMenu(); break;
1842
+ case State.PLAY: updateGame(); drawGame(); drawHUD(); break;
1843
+ case State.OVER: drawGame(); drawHUD(); drawGameOver(); break;
1844
+ }
1845
+ }
1846
+
1847
+ function drawStars() {
1848
+ noStroke();
1849
+ for (const s of stars) {
1850
+ fill(255, s.b);
1851
+ ellipse(s.x, s.y, s.s);
1852
+ s.y += s.s * 0.3;
1853
+ if (s.y > height) { s.y = 0; s.x = random(width); }
1854
+ }
1855
+ }
1856
+
1857
+ function drawMenu() {
1858
+ fill(255);
1859
+ textAlign(CENTER, CENTER);
1860
+ textSize(min(width * 0.08, 56));
1861
+ textStyle(BOLD);
1862
+ text('ASTEROID DODGE', width / 2, height / 2 - 60);
1863
+ textSize(min(width * 0.03, 18));
1864
+ textStyle(NORMAL);
1865
+ fill(180);
1866
+ text('Arrow keys or WASD to move', width / 2, height / 2 + 10);
1867
+ fill(99, 102, 241);
1868
+ text('Press SPACE or ENTER to start', width / 2, height / 2 + 50);
1869
+ if (highScore > 0) {
1870
+ fill(120);
1871
+ textSize(14);
1872
+ text('High Score: ' + highScore, width / 2, height / 2 + 90);
1873
+ }
1874
+ }
1875
+
1876
+ function updateGame() {
1877
+ // Player movement
1878
+ if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) player.x -= player.speed;
1879
+ if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) player.x += player.speed;
1880
+ if (keyIsDown(UP_ARROW) || keyIsDown(87)) player.y -= player.speed;
1881
+ if (keyIsDown(DOWN_ARROW) || keyIsDown(83)) player.y += player.speed;
1882
+ player.x = constrain(player.x, player.size, width - player.size);
1883
+ player.y = constrain(player.y, player.size, height - player.size);
1884
+ if (player.invincible > 0) player.invincible--;
1885
+
1886
+ // Spawn asteroids
1887
+ difficulty = 1 + score / 500;
1888
+ spawnTimer++;
1889
+ if (spawnTimer > max(15, 45 - difficulty * 3)) {
1890
+ asteroids.push({
1891
+ x: random(width), y: -30,
1892
+ size: random(15, 35),
1893
+ vy: random(2, 4) * difficulty,
1894
+ vx: random(-1, 1),
1895
+ rot: random(TWO_PI),
1896
+ rotSpeed: random(-0.05, 0.05),
1897
+ });
1898
+ spawnTimer = 0;
1899
+ }
1900
+
1901
+ // Update asteroids
1902
+ for (let i = asteroids.length - 1; i >= 0; i--) {
1903
+ const a = asteroids[i];
1904
+ a.y += a.vy;
1905
+ a.x += a.vx;
1906
+ a.rot += a.rotSpeed;
1907
+
1908
+ if (a.y > height + 50) {
1909
+ asteroids.splice(i, 1);
1910
+ score += 10;
1911
+ continue;
1912
+ }
1913
+
1914
+ // Collision
1915
+ if (player.invincible <= 0 && dist(player.x, player.y, a.x, a.y) < player.size + a.size / 2) {
1916
+ spawnParticles(a.x, a.y, color(239, 68, 68), 20);
1917
+ asteroids.splice(i, 1);
1918
+ player.lives--;
1919
+ player.invincible = 90;
1920
+ if (player.lives <= 0) {
1921
+ highScore = max(highScore, score);
1922
+ state = State.OVER;
1923
+ }
1924
+ }
1925
+ }
1926
+
1927
+ // Update particles
1928
+ for (let i = particles.length - 1; i >= 0; i--) {
1929
+ const p = particles[i];
1930
+ p.x += p.vx; p.y += p.vy; p.vy += 0.05; p.life -= 0.02;
1931
+ if (p.life <= 0) particles.splice(i, 1);
1932
+ }
1933
+
1934
+ score++;
1935
+ }
1936
+
1937
+ function drawGame() {
1938
+ // Draw asteroids
1939
+ for (const a of asteroids) {
1940
+ push();
1941
+ translate(a.x, a.y);
1942
+ rotate(a.rot);
1943
+ fill(120, 120, 140);
1944
+ stroke(80, 80, 100);
1945
+ strokeWeight(1);
1946
+ beginShape();
1947
+ for (let i = 0; i < 7; i++) {
1948
+ const angle = map(i, 0, 7, 0, TWO_PI);
1949
+ const r = a.size / 2 * (0.7 + 0.3 * sin(i * 2.5));
1950
+ vertex(cos(angle) * r, sin(angle) * r);
1951
+ }
1952
+ endShape(CLOSE);
1953
+ pop();
1954
+ }
1955
+
1956
+ // Draw particles
1957
+ noStroke();
1958
+ for (const p of particles) {
1959
+ fill(red(p.col), green(p.col), blue(p.col), p.life * 255);
1960
+ ellipse(p.x, p.y, p.size * p.life);
1961
+ }
1962
+
1963
+ // Draw player
1964
+ if (state === State.PLAY) {
1965
+ if (player.invincible <= 0 || frameCount % 6 < 3) {
1966
+ push();
1967
+ translate(player.x, player.y);
1968
+ fill(99, 102, 241);
1969
+ noStroke();
1970
+ triangle(0, -player.size, -player.size * 0.6, player.size * 0.6, player.size * 0.6, player.size * 0.6);
1971
+ fill(129, 140, 248);
1972
+ triangle(0, -player.size * 0.5, -player.size * 0.3, player.size * 0.3, player.size * 0.3, player.size * 0.3);
1973
+ pop();
1974
+ }
1975
+ }
1976
+ }
1977
+
1978
+ function drawHUD() {
1979
+ fill(255);
1980
+ noStroke();
1981
+ textAlign(LEFT, TOP);
1982
+ textSize(20);
1983
+ textStyle(BOLD);
1984
+ text('Score: ' + score, 20, 20);
1985
+ textStyle(NORMAL);
1986
+ textSize(14);
1987
+ fill(200);
1988
+ for (let i = 0; i < player.lives; i++) {
1989
+ fill(239, 68, 68);
1990
+ ellipse(20 + i * 22, 55, 14);
1991
+ }
1992
+ }
1993
+
1994
+ function drawGameOver() {
1995
+ fill(0, 0, 0, 150);
1996
+ rect(0, 0, width, height);
1997
+ fill(239, 68, 68);
1998
+ textAlign(CENTER, CENTER);
1999
+ textSize(min(width * 0.07, 48));
2000
+ textStyle(BOLD);
2001
+ text('GAME OVER', width / 2, height / 2 - 40);
2002
+ fill(255);
2003
+ textSize(22);
2004
+ textStyle(NORMAL);
2005
+ text('Score: ' + score, width / 2, height / 2 + 10);
2006
+ fill(180);
2007
+ textSize(16);
2008
+ text('Press SPACE to play again', width / 2, height / 2 + 50);
2009
+ }
2010
+
2011
+ function spawnParticles(x, y, col, count) {
2012
+ for (let i = 0; i < count; i++) {
2013
+ const angle = random(TWO_PI);
2014
+ const speed = random(1, 5);
2015
+ particles.push({
2016
+ x, y, vx: cos(angle) * speed, vy: sin(angle) * speed,
2017
+ size: random(4, 10), col, life: 1.0,
2018
+ });
2019
+ }
2020
+ }
2021
+
2022
+ function keyPressed() {
2023
+ if (state === State.MENU && (key === ' ' || key === 'Enter')) {
2024
+ state = State.PLAY;
2025
+ resetGame();
2026
+ } else if (state === State.OVER && (key === ' ' || key === 'Enter')) {
2027
+ state = State.PLAY;
2028
+ resetGame();
2029
+ }
2030
+ }
2031
+
2032
+ function windowResized() {
2033
+ resizeCanvas(windowWidth, windowHeight);
2034
+ }
2035
+ ```
2036
+
2037
+ ### Example 2: 3D Scene Viewer (Three.js)
2038
+
2039
+ **`.dench.yaml`:**
2040
+ ```yaml
2041
+ name: "3D Playground"
2042
+ description: "Interactive 3D scene with orbit controls"
2043
+ icon: "box"
2044
+ version: "1.0.0"
2045
+ entry: "index.html"
2046
+ runtime: "static"
2047
+ ```
2048
+
2049
+ **`index.html`:**
2050
+ ```html
2051
+ <!DOCTYPE html>
2052
+ <html lang="en">
2053
+ <head>
2054
+ <meta charset="UTF-8">
2055
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2056
+ <title>3D Playground</title>
2057
+ <script type="importmap">
2058
+ {
2059
+ "imports": {
2060
+ "three": "https://unpkg.com/three@0.170/build/three.module.js",
2061
+ "three/addons/": "https://unpkg.com/three@0.170/examples/jsm/"
2062
+ }
2063
+ }
2064
+ </script>
2065
+ <style>
2066
+ * { margin: 0; padding: 0; }
2067
+ html, body { width: 100%; height: 100%; overflow: hidden; }
2068
+ canvas { display: block; }
2069
+ </style>
2070
+ </head>
2071
+ <body>
2072
+ <script type="module" src="scene.js"></script>
2073
+ </body>
2074
+ </html>
2075
+ ```
2076
+
2077
+ **`scene.js`:**
2078
+ ```javascript
2079
+ import * as THREE from 'three';
2080
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
2081
+
2082
+ const scene = new THREE.Scene();
2083
+ let bgColor = 0x0f0f1a;
2084
+
2085
+ if (window.dench) {
2086
+ window.dench.app.getTheme().then(t => {
2087
+ bgColor = t === 'light' ? 0xf0f0f5 : 0x0f0f1a;
2088
+ scene.background = new THREE.Color(bgColor);
2089
+ scene.fog = new THREE.Fog(bgColor, 30, 100);
2090
+ }).catch(() => {});
2091
+ }
2092
+
2093
+ scene.background = new THREE.Color(bgColor);
2094
+ scene.fog = new THREE.Fog(bgColor, 30, 100);
2095
+
2096
+ const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 500);
2097
+ camera.position.set(8, 6, 12);
2098
+
2099
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
2100
+ renderer.setSize(innerWidth, innerHeight);
2101
+ renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
2102
+ renderer.shadowMap.enabled = true;
2103
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
2104
+ document.body.appendChild(renderer.domElement);
2105
+
2106
+ const controls = new OrbitControls(camera, renderer.domElement);
2107
+ controls.enableDamping = true;
2108
+
2109
+ scene.add(new THREE.AmbientLight(0x404060, 0.6));
2110
+ const sun = new THREE.DirectionalLight(0xffffff, 1.5);
2111
+ sun.position.set(10, 20, 10);
2112
+ sun.castShadow = true;
2113
+ sun.shadow.mapSize.set(1024, 1024);
2114
+ scene.add(sun);
2115
+
2116
+ const ground = new THREE.Mesh(
2117
+ new THREE.PlaneGeometry(60, 60),
2118
+ new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.8 })
2119
+ );
2120
+ ground.rotation.x = -Math.PI / 2;
2121
+ ground.receiveShadow = true;
2122
+ scene.add(ground);
2123
+
2124
+ const shapes = [];
2125
+ const colors = [0x6366f1, 0x22c55e, 0xf59e0b, 0xef4444, 0x06b6d4];
2126
+
2127
+ for (let i = 0; i < 12; i++) {
2128
+ const geos = [
2129
+ new THREE.BoxGeometry(1, 1, 1),
2130
+ new THREE.SphereGeometry(0.6, 32, 32),
2131
+ new THREE.ConeGeometry(0.5, 1.2, 6),
2132
+ new THREE.TorusGeometry(0.5, 0.2, 16, 32),
2133
+ new THREE.OctahedronGeometry(0.6),
2134
+ ];
2135
+ const geo = geos[Math.floor(Math.random() * geos.length)];
2136
+ const mat = new THREE.MeshStandardMaterial({
2137
+ color: colors[Math.floor(Math.random() * colors.length)],
2138
+ roughness: 0.3, metalness: 0.5,
2139
+ });
2140
+ const mesh = new THREE.Mesh(geo, mat);
2141
+ mesh.position.set(
2142
+ (Math.random() - 0.5) * 16,
2143
+ 0.5 + Math.random() * 3,
2144
+ (Math.random() - 0.5) * 16
2145
+ );
2146
+ mesh.castShadow = true;
2147
+ mesh.userData = {
2148
+ baseY: mesh.position.y,
2149
+ phase: Math.random() * Math.PI * 2,
2150
+ rotSpeed: (Math.random() - 0.5) * 0.02,
2151
+ };
2152
+ scene.add(mesh);
2153
+ shapes.push(mesh);
2154
+ }
2155
+
2156
+ const clock = new THREE.Clock();
2157
+
2158
+ function animate() {
2159
+ requestAnimationFrame(animate);
2160
+ const t = clock.getElapsedTime();
2161
+
2162
+ for (const s of shapes) {
2163
+ s.position.y = s.userData.baseY + Math.sin(t + s.userData.phase) * 0.4;
2164
+ s.rotation.y += s.userData.rotSpeed;
2165
+ }
2166
+
2167
+ controls.update();
2168
+ renderer.render(scene, camera);
2169
+ }
2170
+ animate();
2171
+
2172
+ addEventListener('resize', () => {
2173
+ camera.aspect = innerWidth / innerHeight;
2174
+ camera.updateProjectionMatrix();
2175
+ renderer.setSize(innerWidth, innerHeight);
2176
+ });
2177
+ ```
2178
+
2179
+ ### Example 3: Data Dashboard
2180
+
2181
+ **`.dench.yaml`:**
2182
+ ```yaml
2183
+ name: "Dashboard"
2184
+ description: "Workspace overview dashboard"
2185
+ icon: "layout-dashboard"
2186
+ version: "1.0.0"
2187
+ entry: "index.html"
2188
+ runtime: "static"
2189
+ permissions:
2190
+ - database
2191
+ ```
2192
+
2193
+ **`index.html`:**
2194
+ ```html
2195
+ <!DOCTYPE html>
2196
+ <html lang="en">
2197
+ <head>
2198
+ <meta charset="UTF-8">
2199
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2200
+ <title>Dashboard</title>
2201
+ <style>
2202
+ * { box-sizing: border-box; margin: 0; padding: 0; }
2203
+ body {
2204
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
2205
+ padding: 24px; transition: background 0.2s, color 0.2s;
2206
+ }
2207
+ body.dark { background: #0f0f1a; color: #e8e8f0; }
2208
+ body.light { background: #fff; color: #1a1a2e; }
2209
+
2210
+ h1 { font-size: 24px; margin-bottom: 24px; }
2211
+ .grid {
2212
+ display: grid;
2213
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
2214
+ gap: 16px;
2215
+ }
2216
+ .card {
2217
+ padding: 20px; border-radius: 12px;
2218
+ background: color-mix(in srgb, currentColor 5%, transparent);
2219
+ border: 1px solid color-mix(in srgb, currentColor 10%, transparent);
2220
+ }
2221
+ .card h3 { font-size: 13px; opacity: 0.6; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
2222
+ .card .value { font-size: 36px; font-weight: 700; font-variant-numeric: tabular-nums; }
2223
+ .error { padding: 16px; background: #ef444420; border-radius: 8px; color: #ef4444; margin-top: 16px; }
2224
+ </style>
2225
+ </head>
2226
+ <body>
2227
+ <h1>Workspace Dashboard</h1>
2228
+ <div class="grid" id="cards"></div>
2229
+ <script>
2230
+ async function init() {
2231
+ try {
2232
+ const theme = await window.dench.app.getTheme();
2233
+ document.body.className = theme;
2234
+ } catch { document.body.className = 'dark'; }
2235
+
2236
+ try {
2237
+ const result = await window.dench.db.query("SELECT name, entry_count FROM objects");
2238
+ const container = document.getElementById('cards');
2239
+ for (const row of result.rows || []) {
2240
+ const card = document.createElement('div');
2241
+ card.className = 'card';
2242
+ card.innerHTML = '<h3>' + row.name + '</h3><div class="value">' + (row.entry_count ?? 0) + '</div>';
2243
+ container.appendChild(card);
2244
+ }
2245
+ if (!result.rows?.length) {
2246
+ container.innerHTML = '<p style="opacity:0.5">No objects in workspace yet.</p>';
2247
+ }
2248
+ } catch (err) {
2249
+ document.getElementById('cards').innerHTML =
2250
+ '<div class="error">Error loading data: ' + err.message + '</div>';
2251
+ }
2252
+ }
2253
+ init();
2254
+ </script>
2255
+ </body>
2256
+ </html>
2257
+ ```
2258
+
2259
+ ---
2260
+
2261
+ ## DuckDB Data Integration
2262
+
2263
+ Apps with the `database` permission can query the workspace DuckDB.
2264
+
2265
+ ### Common Queries
2266
+
2267
+ ```javascript
2268
+ // List all objects (tables)
2269
+ const objects = await window.dench.db.query("SELECT * FROM objects");
2270
+
2271
+ // Get entries from an object by name
2272
+ const people = await window.dench.db.query(
2273
+ "SELECT * FROM entries WHERE object_id = (SELECT id FROM objects WHERE name = 'people')"
2274
+ );
2275
+
2276
+ // Get field definitions
2277
+ const fields = await window.dench.db.query(
2278
+ "SELECT * FROM fields WHERE object_id = (SELECT id FROM objects WHERE name = 'people')"
2279
+ );
2280
+
2281
+ // Use PIVOT views for tabular display
2282
+ const data = await window.dench.db.query("SELECT * FROM pivot_people");
2283
+
2284
+ // Aggregate queries
2285
+ const stats = await window.dench.db.query(`
2286
+ SELECT
2287
+ o.name,
2288
+ COUNT(e.id) as count,
2289
+ MIN(e.created_at) as earliest,
2290
+ MAX(e.created_at) as latest
2291
+ FROM objects o
2292
+ LEFT JOIN entries e ON e.object_id = o.id
2293
+ GROUP BY o.name
2294
+ ORDER BY count DESC
2295
+ `);
2296
+ ```
2297
+
2298
+ ### Creating App-Specific Tables
2299
+
2300
+ Apps can create their own tables for storing app-specific data:
2301
+
2302
+ ```javascript
2303
+ await window.dench.db.execute(`
2304
+ CREATE TABLE IF NOT EXISTS app_settings (
2305
+ key TEXT PRIMARY KEY,
2306
+ value TEXT,
2307
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
2308
+ )
2309
+ `);
2310
+
2311
+ await window.dench.db.execute(
2312
+ `INSERT OR REPLACE INTO app_settings (key, value) VALUES ('theme_preference', 'dark')`
2313
+ );
2314
+ ```
2315
+
2316
+ ---
2317
+
2318
+ ## Creating an App — Step by Step Checklist
2319
+
2320
+ When asked to build an app, follow these steps:
2321
+
2322
+ 1. **Determine the app type** — game (2D/3D), dashboard, tool, visualization, etc.
2323
+ 2. **Choose the right library**:
2324
+ - 2D game / simulation / generative art → **p5.js** (always)
2325
+ - 3D game / scene / visualization → **Three.js** (always)
2326
+ - Data dashboard → **Chart.js** or **plain HTML/CSS**
2327
+ - Interactive tool / form → **plain HTML/CSS/JS**
2328
+ 3. **Create the app folder**: `apps/<name>.dench.app/`
2329
+ 4. **Create `.dench.yaml`** with manifest (always include `name`, `entry`, `runtime`)
2330
+ 5. **Create `index.html`** as the entry point with CDN script tags
2331
+ 6. **Create separate JS file(s)** for app logic — avoid massive inline scripts
2332
+ 7. **Apply theme** via `window.dench.app.getTheme()` on init
2333
+ 8. **Handle window resizing** (canvas-based apps must call `resizeCanvas` / update renderer)
2334
+ 9. **Add error handling** for all bridge API calls
2335
+ 10. **Test the app** opens correctly as a tab in DenchClaw