@jlongo78/agent-spaces 0.9.6 → 0.9.8

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 (917) hide show
  1. package/.next/standalone/.claude/settings.local.json +68 -0
  2. package/.next/standalone/.claude/spaces-env.json +1 -0
  3. package/.next/standalone/.next/BUILD_ID +1 -1
  4. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  5. package/.next/standalone/.next/build-manifest.json +2 -2
  6. package/.next/standalone/.next/prerender-manifest.json +3 -3
  7. package/.next/standalone/.next/required-server-files.json +19 -19
  8. package/.next/standalone/.next/routes-manifest.json +6 -0
  9. package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  26. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  27. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  28. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  33. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  34. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  35. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  36. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  38. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
  42. package/.next/standalone/.next/server/app/admin/analytics.rsc +2 -2
  43. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +1 -1
  44. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
  46. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  47. package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +2 -2
  48. package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
  49. package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
  50. package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
  51. package/.next/standalone/.next/server/app/admin/users.html +1 -1
  52. package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
  53. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
  55. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
  56. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  57. package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
  59. package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
  60. package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
  61. package/.next/standalone/.next/server/app/analytics.html +1 -1
  62. package/.next/standalone/.next/server/app/analytics.rsc +2 -2
  63. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +1 -1
  64. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
  65. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  66. package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +2 -2
  67. package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
  68. package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
  69. package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
  70. package/.next/standalone/.next/server/app/api/analytics/overview/route.js +1 -1
  71. package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/api/benchmark/lobes/route.js +1 -1
  73. package/.next/standalone/.next/server/app/api/benchmark/lobes/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/api/benchmark/run/route.js +1 -1
  75. package/.next/standalone/.next/server/app/api/benchmark/run/route.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route.js +1 -1
  77. package/.next/standalone/.next/server/app/api/benchmark/runs/[id]/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/api/benchmark/runs/route.js +1 -1
  79. package/.next/standalone/.next/server/app/api/benchmark/runs/route.js.nft.json +1 -1
  80. package/.next/standalone/.next/server/app/api/benchmark/status/route.js +1 -1
  81. package/.next/standalone/.next/server/app/api/benchmark/status/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/bulk/route.js +1 -1
  83. package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/config/route.js +1 -1
  85. package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/api/cortex/context/route.js +1 -1
  87. package/.next/standalone/.next/server/app/api/cortex/context/route.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js +1 -1
  89. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.nft.json +1 -1
  90. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js +1 -1
  91. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js +1 -1
  93. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js +1 -1
  95. package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.nft.json +1 -1
  96. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js +1 -1
  97. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/api/cortex/export/route.js +1 -1
  99. package/.next/standalone/.next/server/app/api/cortex/export/route.js.nft.json +1 -1
  100. package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js +1 -1
  101. package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js.nft.json +1 -1
  102. package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js +1 -1
  103. package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js.nft.json +1 -1
  104. package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js +1 -1
  105. package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js +1 -1
  107. package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js.nft.json +1 -1
  108. package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js +1 -1
  109. package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js.nft.json +1 -1
  110. package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js +1 -1
  111. package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js +1 -1
  113. package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/cortex/import/route.js +1 -1
  117. package/.next/standalone/.next/server/app/api/cortex/import/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/cortex/import/status/route.js +1 -1
  119. package/.next/standalone/.next/server/app/api/cortex/import/status/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js +1 -1
  121. package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js +1 -1
  123. package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js +1 -1
  125. package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js.nft.json +1 -1
  126. package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js +1 -1
  127. package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js.nft.json +1 -1
  128. package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js +1 -1
  129. package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/api/cortex/lobes/route.js +1 -1
  131. package/.next/standalone/.next/server/app/api/cortex/lobes/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js +1 -1
  133. package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js +1 -1
  135. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.nft.json +1 -1
  136. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js +1 -1
  137. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js +1 -1
  139. package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js.nft.json +1 -1
  140. package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js +1 -1
  141. package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/api/cortex/search/route.js +1 -1
  143. package/.next/standalone/.next/server/app/api/cortex/search/route.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/api/cortex/settings/route.js +1 -1
  145. package/.next/standalone/.next/server/app/api/cortex/settings/route.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/api/cortex/status/route.js +1 -1
  147. package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/api/cortex/timeline/route.js +1 -1
  149. package/.next/standalone/.next/server/app/api/cortex/timeline/route.js.nft.json +1 -1
  150. package/.next/standalone/.next/server/app/api/cortex/usage/route.js +1 -1
  151. package/.next/standalone/.next/server/app/api/cortex/usage/route.js.nft.json +1 -1
  152. package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js +1 -1
  153. package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js.nft.json +1 -1
  154. package/.next/standalone/.next/server/app/api/events/route.js +1 -1
  155. package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
  156. package/.next/standalone/.next/server/app/api/files/route.js +1 -1
  157. package/.next/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  158. package/.next/standalone/.next/server/app/api/folders/route.js +1 -1
  159. package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
  160. package/.next/standalone/.next/server/app/api/network/handshake/route.js +1 -1
  161. package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
  162. package/.next/standalone/.next/server/app/api/network/panes/[id]/route.js +1 -1
  163. package/.next/standalone/.next/server/app/api/network/panes/[id]/route.js.nft.json +1 -1
  164. package/.next/standalone/.next/server/app/api/network/panes/route.js +1 -1
  165. package/.next/standalone/.next/server/app/api/network/panes/route.js.nft.json +1 -1
  166. package/.next/standalone/.next/server/app/api/network/projects/route.js +1 -1
  167. package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
  168. package/.next/standalone/.next/server/app/api/network/search/route.js +1 -1
  169. package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
  170. package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js +1 -1
  171. package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
  172. package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js +1 -1
  173. package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
  174. package/.next/standalone/.next/server/app/api/network/sessions/route.js +1 -1
  175. package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
  176. package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js +1 -1
  177. package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
  178. package/.next/standalone/.next/server/app/api/network/workspaces/route.js +1 -1
  179. package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
  180. package/.next/standalone/.next/server/app/api/panes/[id]/diff/route.js +1 -1
  181. package/.next/standalone/.next/server/app/api/panes/[id]/diff/route.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/api/panes/[id]/route.js +1 -1
  183. package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
  184. package/.next/standalone/.next/server/app/api/panes/route.js +1 -1
  185. package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
  186. package/.next/standalone/.next/server/app/api/projects/route.js +1 -1
  187. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  188. package/.next/standalone/.next/server/app/api/proxy/models/[modelId]/[...path]/route.js +1 -1
  189. package/.next/standalone/.next/server/app/api/proxy/models/[modelId]/[...path]/route.js.nft.json +1 -1
  190. package/.next/standalone/.next/server/app/api/proxy/models/[modelId]/status/route.js +1 -1
  191. package/.next/standalone/.next/server/app/api/proxy/models/[modelId]/status/route.js.nft.json +1 -1
  192. package/.next/standalone/.next/server/app/api/search/route.js +2 -2
  193. package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  194. package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js +1 -1
  195. package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
  196. package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js +1 -1
  197. package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
  198. package/.next/standalone/.next/server/app/api/sessions/[id]/route.js +1 -1
  199. package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  200. package/.next/standalone/.next/server/app/api/sessions/route.js +2 -2
  201. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  202. package/.next/standalone/.next/server/app/api/sync/route.js +1 -1
  203. package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
  204. package/.next/standalone/.next/server/app/api/tags/route.js +1 -1
  205. package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
  206. package/.next/standalone/.next/server/app/api/tier/route.js +1 -1
  207. package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
  208. package/.next/standalone/.next/server/app/api/whisper/config/route.js +1 -1
  209. package/.next/standalone/.next/server/app/api/whisper/config/route.js.nft.json +1 -1
  210. package/.next/standalone/.next/server/app/api/whisper/route.js.nft.json +1 -1
  211. package/.next/standalone/.next/server/app/api/wizard/chart/route/app-paths-manifest.json +3 -0
  212. package/.next/standalone/.next/server/app/api/wizard/chart/route/build-manifest.json +11 -0
  213. package/.next/standalone/.next/server/app/api/wizard/chart/route/server-reference-manifest.json +4 -0
  214. package/.next/standalone/.next/server/app/api/wizard/chart/route.js +7 -0
  215. package/.next/standalone/.next/server/app/api/wizard/chart/route.js.map +5 -0
  216. package/.next/standalone/.next/server/app/api/wizard/chart/route.js.nft.json +1 -0
  217. package/.next/standalone/.next/server/app/api/wizard/chart/route_client-reference-manifest.js +2 -0
  218. package/.next/standalone/.next/server/app/api/wizard/chat/route.js +1 -1
  219. package/.next/standalone/.next/server/app/api/wizard/chat/route.js.nft.json +1 -1
  220. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js +1 -1
  221. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
  222. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js +1 -1
  223. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
  224. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js +1 -1
  225. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
  226. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js +1 -1
  227. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
  228. package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js +1 -1
  229. package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
  230. package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js +1 -1
  231. package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
  232. package/.next/standalone/.next/server/app/api/workspaces/route.js +2 -2
  233. package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  234. package/.next/standalone/.next/server/app/cortex.html +1 -1
  235. package/.next/standalone/.next/server/app/cortex.rsc +3 -3
  236. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +2 -2
  237. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
  238. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  239. package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +3 -3
  240. package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
  241. package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
  242. package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
  243. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  244. package/.next/standalone/.next/server/app/login.html +1 -1
  245. package/.next/standalone/.next/server/app/login.rsc +2 -2
  246. package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
  247. package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
  248. package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
  249. package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  250. package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  251. package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
  252. package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
  253. package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
  254. package/.next/standalone/.next/server/app/m/projects.html +1 -1
  255. package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
  256. package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
  257. package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
  258. package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
  259. package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
  260. package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +1 -1
  261. package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
  262. package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +1 -1
  263. package/.next/standalone/.next/server/app/m/sessions/[id]/page.js.nft.json +1 -1
  264. package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
  265. package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
  266. package/.next/standalone/.next/server/app/m/sessions.html +1 -1
  267. package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
  268. package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
  269. package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
  270. package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
  271. package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
  272. package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +1 -1
  273. package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
  274. package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +1 -1
  275. package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
  276. package/.next/standalone/.next/server/app/m/settings.html +1 -1
  277. package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
  278. package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
  279. package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
  280. package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
  281. package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
  282. package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +1 -1
  283. package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
  284. package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +1 -1
  285. package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
  286. package/.next/standalone/.next/server/app/m/terminal.html +1 -1
  287. package/.next/standalone/.next/server/app/m/terminal.rsc +3 -3
  288. package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +3 -3
  289. package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
  290. package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
  291. package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
  292. package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +2 -2
  293. package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
  294. package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +1 -1
  295. package/.next/standalone/.next/server/app/m.html +1 -1
  296. package/.next/standalone/.next/server/app/m.rsc +2 -2
  297. package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
  298. package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
  299. package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
  300. package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
  301. package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +1 -1
  302. package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +1 -1
  303. package/.next/standalone/.next/server/app/network.html +1 -1
  304. package/.next/standalone/.next/server/app/network.rsc +2 -2
  305. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +1 -1
  306. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
  307. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  308. package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
  309. package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
  310. package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
  311. package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
  312. package/.next/standalone/.next/server/app/projects.html +1 -1
  313. package/.next/standalone/.next/server/app/projects.rsc +2 -2
  314. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +1 -1
  315. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
  316. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  317. package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
  318. package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
  319. package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
  320. package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
  321. package/.next/standalone/.next/server/app/sessions.html +1 -1
  322. package/.next/standalone/.next/server/app/sessions.rsc +2 -2
  323. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +1 -1
  324. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
  325. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  326. package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
  327. package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
  328. package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
  329. package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
  330. package/.next/standalone/.next/server/app/settings.html +1 -1
  331. package/.next/standalone/.next/server/app/settings.rsc +2 -2
  332. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +1 -1
  333. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
  334. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  335. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  336. package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  337. package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  338. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  339. package/.next/standalone/.next/server/app/terminal.html +1 -1
  340. package/.next/standalone/.next/server/app/terminal.rsc +3 -3
  341. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
  342. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
  343. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  344. package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +3 -3
  345. package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
  346. package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
  347. package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
  348. package/.next/standalone/.next/server/app/vr/page/react-loadable-manifest.json +1 -1
  349. package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +1 -1
  350. package/.next/standalone/.next/server/app/vr.html +1 -1
  351. package/.next/standalone/.next/server/app/vr.rsc +3 -3
  352. package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +3 -3
  353. package/.next/standalone/.next/server/app/vr.segments/_head.segment.rsc +1 -1
  354. package/.next/standalone/.next/server/app/vr.segments/_index.segment.rsc +2 -2
  355. package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +2 -2
  356. package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +2 -2
  357. package/.next/standalone/.next/server/app/vr.segments/vr.segment.rsc +1 -1
  358. package/.next/standalone/.next/server/app/workspaces.html +1 -1
  359. package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
  360. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +1 -1
  361. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
  362. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  363. package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
  364. package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
  365. package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
  366. package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
  367. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  368. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00e90fc6._.js +98 -0
  369. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01ab8675._.js +98 -0
  370. package/.next/standalone/.next/server/chunks/[root-of-the-server]__03974f05._.js +98 -0
  371. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__bb331da9._.js → [root-of-the-server]__046c9b91._.js} +3 -3
  372. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04ae6bf0._.js +98 -0
  373. package/.next/standalone/.next/server/chunks/[root-of-the-server]__056fa416._.js +1 -1
  374. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ac4ea3f._.js +3 -0
  375. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b8e64cb._.js +98 -0
  376. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__d95165f0._.js → [root-of-the-server]__0facd39e._.js} +3 -3
  377. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10bc76a3._.js +3 -0
  378. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__9d68157b._.js → [root-of-the-server]__115f3934._.js} +3 -3
  379. package/.next/standalone/.next/server/chunks/[root-of-the-server]__11f155f1._.js +3 -0
  380. package/.next/standalone/.next/server/chunks/[root-of-the-server]__160e7c73._.js +22 -33
  381. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__91e23c96._.js → [root-of-the-server]__17a3b966._.js} +3 -3
  382. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__277d9445._.js → [root-of-the-server]__17d3a2b2._.js} +3 -3
  383. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a86c055._.js +98 -0
  384. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20b5e9c4._.js +3 -0
  385. package/.next/standalone/.next/server/chunks/[root-of-the-server]__28d6fbd8._.js +98 -0
  386. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04f04898._.js → [root-of-the-server]__2a3f866b._.js} +2 -2
  387. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__a95bb38b._.js → [root-of-the-server]__316617e7._.js} +2 -2
  388. package/.next/standalone/.next/server/chunks/[root-of-the-server]__32ad8f71._.js +98 -0
  389. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__6fe5e6c8._.js → [root-of-the-server]__35457394._.js} +2 -2
  390. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1f054c65._.js → [root-of-the-server]__35de78e6._.js} +3 -3
  391. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3685ffcb._.js +98 -0
  392. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__614458b7._.js → [root-of-the-server]__38954988._.js} +3 -3
  393. package/.next/standalone/.next/server/chunks/[root-of-the-server]__426ad936._.js +106 -0
  394. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4985c034._.js +98 -0
  395. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c6ce9ed._.js +98 -0
  396. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5cebe58a._.js +98 -0
  397. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5d5e4789._.js +98 -0
  398. package/.next/standalone/.next/server/chunks/[root-of-the-server]__65676930._.js +3 -0
  399. package/.next/standalone/.next/server/chunks/[root-of-the-server]__67cab326._.js +58 -0
  400. package/.next/standalone/.next/server/chunks/[root-of-the-server]__698c6f01._.js +98 -0
  401. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c64af29._.js +131 -0
  402. package/.next/standalone/.next/server/chunks/[root-of-the-server]__73aed9f5._.js +98 -0
  403. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__ac84b704._.js → [root-of-the-server]__79b6a9bb._.js} +3 -3
  404. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__e56abacf._.js → [root-of-the-server]__7db704c6._.js} +4 -4
  405. package/.next/standalone/.next/server/chunks/[root-of-the-server]__812ca02b._.js +98 -0
  406. package/.next/standalone/.next/server/chunks/[root-of-the-server]__821f50fa._.js +98 -0
  407. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8716b86e._.js +98 -0
  408. package/.next/standalone/.next/server/chunks/[root-of-the-server]__884ef754._.js +98 -0
  409. package/.next/standalone/.next/server/chunks/[root-of-the-server]__88cdbd68._.js +98 -0
  410. package/.next/standalone/.next/server/chunks/[root-of-the-server]__89d9aba9._.js +98 -0
  411. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d536cb5._.js +98 -0
  412. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8df8c5d1._.js +98 -0
  413. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__2d7a454e._.js → [root-of-the-server]__8f2ccc41._.js} +3 -3
  414. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__2a2c5fc5._.js → [root-of-the-server]__95c9d682._.js} +4 -4
  415. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e5d7774._.js +98 -0
  416. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__cc6e2885._.js → [root-of-the-server]__9edcff87._.js} +2 -2
  417. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a049dfc2._.js +98 -0
  418. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a5b4bb9a._.js +98 -0
  419. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__929ea03a._.js → [root-of-the-server]__a83262a1._.js} +2 -2
  420. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9cd1240._.js +98 -0
  421. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d7f822._.js +98 -0
  422. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad08c221._.js +98 -0
  423. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad585f2f._.js +98 -0
  424. package/.next/standalone/.next/server/chunks/[root-of-the-server]__afcb8f7d._.js +98 -0
  425. package/.next/standalone/.next/server/chunks/[root-of-the-server]__bc250d43._.js +98 -0
  426. package/.next/standalone/.next/server/chunks/[root-of-the-server]__bce2a6e7._.js +98 -0
  427. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c011bf91._.js +98 -0
  428. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c0ac2895._.js +3 -0
  429. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c130a00c._.js +1 -1
  430. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c37d6380._.js +3 -0
  431. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cae392eb._.js +98 -0
  432. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cc2616bb._.js +3 -3
  433. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d501fa9b._.js +98 -0
  434. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d59c6c15._.js +98 -0
  435. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d5c1db32._.js +98 -0
  436. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d5d92527._.js +1 -1
  437. package/.next/standalone/.next/server/chunks/[root-of-the-server]__dba60c86._.js +98 -0
  438. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__2384f98e._.js → [root-of-the-server]__de14b9ae._.js} +3 -3
  439. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e10643d1._.js +98 -0
  440. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__00fdfbda._.js → [root-of-the-server]__e2a996e5._.js} +2 -2
  441. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__4d903941._.js → [root-of-the-server]__e3477417._.js} +3 -3
  442. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__32dc5513._.js → [root-of-the-server]__e4db362e._.js} +2 -2
  443. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__49e42a3a._.js → [root-of-the-server]__e4e70b86._.js} +3 -3
  444. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__ac39ecc7._.js → [root-of-the-server]__e54925af._.js} +3 -3
  445. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e8edc5b0._.js +98 -0
  446. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__eafd040b._.js → [root-of-the-server]__eab4d83b._.js} +2 -2
  447. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ead29015._.js +1 -1
  448. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__194955d4._.js → [root-of-the-server]__f056fd83._.js} +3 -3
  449. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f0e99572._.js +98 -0
  450. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fe1e16d0._.js +98 -0
  451. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff9cd277._.js +98 -0
  452. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ffaea2ce._.js +98 -0
  453. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wizard_chart_route_actions_888e2ec1.js +3 -0
  454. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__2cffc362._.js +3 -0
  455. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__f1050870._.js → [root-of-the-server]__47c97637._.js} +2 -2
  456. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
  457. package/.next/standalone/.next/server/chunks/ssr/_17b946fd._.js +3 -0
  458. package/.next/standalone/.next/server/chunks/ssr/_2a1d79e7._.js +1 -1
  459. package/.next/standalone/.next/server/chunks/ssr/_5c3c4cfa._.js +7 -5
  460. package/.next/standalone/.next/server/chunks/ssr/_ba432382._.js +7 -5
  461. package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_cortex_page_tsx_0f33d8b3._.js +1 -1
  462. package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_terminal_page_tsx_de5e8d85._.js +4 -4
  463. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__90eeddae._.js +1 -1
  464. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  465. package/.next/standalone/.next/server/pages/404.html +1 -1
  466. package/.next/standalone/.next/server/pages/500.html +2 -2
  467. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  468. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  469. package/.next/standalone/.next/static/chunks/0852575eb90c1e8d.js +85 -0
  470. package/.next/standalone/.next/static/chunks/{74d0ac0b1d0a1b79.js → 10b00b4f66102dcf.js} +1 -1
  471. package/.next/standalone/.next/static/chunks/{16eb77953dee9ea3.js → 19c71a8376c23c58.js} +1 -1
  472. package/.next/standalone/.next/static/chunks/2b769d1597e4fc1c.css +3 -0
  473. package/.next/standalone/.next/static/chunks/{3e91fc608659c524.js → 350271fe79509caf.js} +1 -1
  474. package/.next/standalone/.next/static/chunks/{70423c7afd8abf5f.js → 597847c22200c212.js} +1 -1
  475. package/.next/standalone/.next/static/chunks/{fb0abd1933b2b2e1.js → 7f6a14f1849fa94d.js} +1 -1
  476. package/.next/standalone/.next/static/chunks/{7ecd9bbb0ce4d68a.js → b7c8fe9b7275a84f.js} +1 -1
  477. package/.next/standalone/.next/static/chunks/{6245135a7afb8c7b.js → c9b10fc55516d142.js} +8 -6
  478. package/.next/standalone/.next/static/chunks/d0065f48eab94944.js +1 -0
  479. package/.next/standalone/.next/static/chunks/{180c1b9ff31b979f.js → f7b34c807badf95d.js} +8 -6
  480. package/.next/standalone/.spaces/cortex-context.md +50 -144
  481. package/.next/standalone/LICENSE +661 -661
  482. package/.next/standalone/NOTICE +5 -5
  483. package/.next/standalone/README.md +131 -131
  484. package/.next/standalone/bin/cortex-hook.js +79 -79
  485. package/.next/standalone/bin/cortex-hook.sh +62 -62
  486. package/.next/standalone/bin/cortex-learn-hook.js +138 -138
  487. package/.next/standalone/bin/cortex-mcp.js +60 -60
  488. package/.next/standalone/bin/cortex-pi-extension.ts +170 -170
  489. package/.next/standalone/bin/fix-standalone-externals.js +79 -79
  490. package/.next/standalone/bin/lib/auto-setup.js +110 -110
  491. package/.next/standalone/bin/mdns-service.js +171 -171
  492. package/.next/standalone/bin/postinstall.js +35 -35
  493. package/.next/standalone/bin/setup-admin.js +195 -195
  494. package/.next/standalone/bin/spaces-dev.js +247 -247
  495. package/.next/standalone/bin/spaces-install.js +660 -660
  496. package/.next/standalone/bin/spaces-postinstall.js +50 -50
  497. package/.next/standalone/bin/spaces-reset-totp.js +50 -50
  498. package/.next/standalone/bin/spaces-service.js +1046 -1046
  499. package/.next/standalone/bin/spaces-setup.js +253 -253
  500. package/.next/standalone/bin/spaces.js +808 -805
  501. package/.next/standalone/bin/ssh-auth-keys.sh +68 -68
  502. package/.next/standalone/bin/terminal-server.js +2819 -2781
  503. package/.next/standalone/cortex-hook-debug.log +57 -23
  504. package/.next/standalone/docker-compose.yml +28 -28
  505. package/.next/standalone/docs/architecture.md +387 -387
  506. package/.next/standalone/docs/cortex-integration-reference.md +268 -268
  507. package/.next/standalone/docs/cortex.md +293 -293
  508. package/.next/standalone/docs/getting-started.md +96 -96
  509. package/.next/standalone/docs/plans/2026-02-24-multi-agent-sessions-design.md +133 -133
  510. package/.next/standalone/docs/plans/2026-02-24-multi-agent-sessions-plan.md +959 -959
  511. package/.next/standalone/docs/plans/2026-03-02-security-audit.md +229 -229
  512. package/.next/standalone/docs/plans/2026-03-07-service-command-design.md +146 -146
  513. package/.next/standalone/docs/plans/2026-03-07-service-command-plan.md +254 -254
  514. package/.next/standalone/docs/server-install.md +564 -564
  515. package/.next/standalone/docs/social-card.html +150 -150
  516. package/.next/standalone/docs/superpowers/plans/2026-03-12-spaces-cortex.md +5270 -5270
  517. package/.next/standalone/docs/superpowers/plans/2026-03-13-cortex-wiring.md +1387 -1387
  518. package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-entity-graph.md +1923 -1923
  519. package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-knowledge-evolution.md +1113 -1113
  520. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-boundary-engine.md +853 -853
  521. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-context-engine.md +1274 -1274
  522. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-signal-ingestion.md +933 -933
  523. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-lobes.md +1080 -1080
  524. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-gravity-system.md +768 -768
  525. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-ui.md +1108 -1108
  526. package/.next/standalone/docs/superpowers/plans/2026-03-18-cortex-ui-integration.md +1846 -1846
  527. package/.next/standalone/docs/superpowers/plans/2026-03-19-vr-phase1-shell.md +1639 -1639
  528. package/.next/standalone/docs/superpowers/plans/2026-03-27-dockview-pane-layout.md +98 -98
  529. package/.next/standalone/docs/superpowers/specs/2026-03-11-universe-view-design.md +320 -320
  530. package/.next/standalone/docs/superpowers/specs/2026-03-12-spaces-brain-design.md +720 -720
  531. package/.next/standalone/docs/superpowers/specs/2026-03-13-cortex-wiring-design.md +268 -268
  532. package/.next/standalone/docs/superpowers/specs/2026-03-14-cortex-v2-design.md +623 -623
  533. package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-lobes-design.md +263 -263
  534. package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md +240 -240
  535. package/.next/standalone/docs/superpowers/specs/2026-03-16-pane-ux-design.md +77 -77
  536. package/.next/standalone/docs/superpowers/specs/2026-03-18-cortex-ui-integration-design.md +341 -341
  537. package/.next/standalone/docs/superpowers/specs/2026-03-19-vr-phase1-shell-design.md +288 -288
  538. package/.next/standalone/docs/superpowers/specs/2026-03-27-pane-diff-review-and-project-wizard-design.md +322 -322
  539. package/.next/standalone/docs/tiers.md +104 -104
  540. package/.next/standalone/eslint.config.mjs +18 -18
  541. package/.next/standalone/next.config.ts +20 -20
  542. package/.next/standalone/nginx.conf +53 -53
  543. package/.next/standalone/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64.node +0 -0
  544. package/.next/standalone/node_modules/@img/{sharp-linux-x64 → sharp-win32-x64}/package.json +39 -46
  545. package/.next/standalone/package-lock.json +14985 -14986
  546. package/.next/standalone/package.json +111 -111
  547. package/.next/standalone/postcss.config.mjs +7 -7
  548. package/.next/standalone/scripts/rebuild.cmd +65 -65
  549. package/.next/standalone/scripts/rebuild.sh +59 -59
  550. package/.next/standalone/server.js +1 -1
  551. package/.next/standalone/src/app/(desktop)/admin/analytics/page.tsx +266 -266
  552. package/.next/standalone/src/app/(desktop)/admin/users/page.tsx +399 -399
  553. package/.next/standalone/src/app/(desktop)/analytics/page.tsx +166 -166
  554. package/.next/standalone/src/app/(desktop)/cortex/page.tsx +81 -81
  555. package/.next/standalone/src/app/(desktop)/dashboard-client.tsx +56 -56
  556. package/.next/standalone/src/app/(desktop)/layout.tsx +18 -18
  557. package/.next/standalone/src/app/(desktop)/network/page.tsx +137 -137
  558. package/.next/standalone/src/app/(desktop)/page.tsx +17 -17
  559. package/.next/standalone/src/app/(desktop)/projects/page.tsx +68 -68
  560. package/.next/standalone/src/app/(desktop)/sessions/[id]/page.tsx +519 -519
  561. package/.next/standalone/src/app/(desktop)/sessions/page.tsx +145 -145
  562. package/.next/standalone/src/app/(desktop)/settings/page.tsx +446 -446
  563. package/.next/standalone/src/app/(desktop)/terminal/layout.tsx +7 -7
  564. package/.next/standalone/src/app/(desktop)/terminal/page.tsx +1330 -1291
  565. package/.next/standalone/src/app/(desktop)/terminal/pane/[id]/page.tsx +211 -211
  566. package/.next/standalone/src/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.tsx +252 -252
  567. package/.next/standalone/src/app/(desktop)/workspaces/page.tsx +12 -12
  568. package/.next/standalone/src/app/api/admin/analytics/route.ts +10 -10
  569. package/.next/standalone/src/app/api/admin/users/[id]/route.ts +20 -20
  570. package/.next/standalone/src/app/api/admin/users/route.ts +15 -15
  571. package/.next/standalone/src/app/api/analytics/overview/route.ts +80 -80
  572. package/.next/standalone/src/app/api/auth/login/route.ts +10 -10
  573. package/.next/standalone/src/app/api/auth/logout/route.ts +9 -9
  574. package/.next/standalone/src/app/api/auth/me/route.ts +22 -22
  575. package/.next/standalone/src/app/api/auth/totp/setup/route.ts +10 -10
  576. package/.next/standalone/src/app/api/auth/totp/status/route.ts +10 -10
  577. package/.next/standalone/src/app/api/auth/totp/verify/route.ts +10 -10
  578. package/.next/standalone/src/app/api/benchmark/lobes/route.ts +16 -16
  579. package/.next/standalone/src/app/api/benchmark/run/route.ts +113 -92
  580. package/.next/standalone/src/app/api/benchmark/runs/[id]/route.ts +26 -26
  581. package/.next/standalone/src/app/api/benchmark/runs/route.ts +16 -16
  582. package/.next/standalone/src/app/api/benchmark/status/route.ts +35 -35
  583. package/.next/standalone/src/app/api/bulk/route.ts +34 -34
  584. package/.next/standalone/src/app/api/chat/route.ts +85 -85
  585. package/.next/standalone/src/app/api/config/route.ts +30 -30
  586. package/.next/standalone/src/app/api/cortex/context/route.ts +78 -78
  587. package/.next/standalone/src/app/api/cortex/curation/assess/route.ts +27 -27
  588. package/.next/standalone/src/app/api/cortex/curation/publish/route.ts +23 -23
  589. package/.next/standalone/src/app/api/cortex/curation/refine/route.ts +23 -23
  590. package/.next/standalone/src/app/api/cortex/curation/review/route.ts +29 -29
  591. package/.next/standalone/src/app/api/cortex/curation/seed/route.ts +23 -23
  592. package/.next/standalone/src/app/api/cortex/export/route.ts +40 -40
  593. package/.next/standalone/src/app/api/cortex/federation/pending/route.ts +20 -20
  594. package/.next/standalone/src/app/api/cortex/federation/resolve/route.ts +43 -43
  595. package/.next/standalone/src/app/api/cortex/federation/search/route.ts +35 -35
  596. package/.next/standalone/src/app/api/cortex/federation/teach/route.ts +76 -76
  597. package/.next/standalone/src/app/api/cortex/graph/edges/route.ts +112 -112
  598. package/.next/standalone/src/app/api/cortex/graph/entities/[id]/route.ts +73 -73
  599. package/.next/standalone/src/app/api/cortex/graph/entities/route.ts +75 -75
  600. package/.next/standalone/src/app/api/cortex/graph/populate/route.ts +203 -203
  601. package/.next/standalone/src/app/api/cortex/import/route.ts +75 -75
  602. package/.next/standalone/src/app/api/cortex/import/status/route.ts +15 -15
  603. package/.next/standalone/src/app/api/cortex/ingest/bootstrap/route.ts +39 -39
  604. package/.next/standalone/src/app/api/cortex/ingest/status/route.ts +15 -15
  605. package/.next/standalone/src/app/api/cortex/knowledge/[id]/route.ts +91 -91
  606. package/.next/standalone/src/app/api/cortex/knowledge/route.ts +97 -97
  607. package/.next/standalone/src/app/api/cortex/lobes/[id]/route.ts +67 -67
  608. package/.next/standalone/src/app/api/cortex/lobes/route.ts +22 -22
  609. package/.next/standalone/src/app/api/cortex/lobes/share/route.ts +80 -80
  610. package/.next/standalone/src/app/api/cortex/marketplace/browse/route.ts +43 -43
  611. package/.next/standalone/src/app/api/cortex/marketplace/preview/route.ts +46 -46
  612. package/.next/standalone/src/app/api/cortex/mcp/call/route.ts +11 -11
  613. package/.next/standalone/src/app/api/cortex/mcp/tools/route.ts +8 -8
  614. package/.next/standalone/src/app/api/cortex/search/route.ts +57 -45
  615. package/.next/standalone/src/app/api/cortex/settings/route.ts +35 -35
  616. package/.next/standalone/src/app/api/cortex/status/route.ts +169 -169
  617. package/.next/standalone/src/app/api/cortex/timeline/route.ts +42 -42
  618. package/.next/standalone/src/app/api/cortex/usage/route.ts +31 -31
  619. package/.next/standalone/src/app/api/cortex/workspace/[id]/context/route.ts +41 -41
  620. package/.next/standalone/src/app/api/events/route.ts +40 -40
  621. package/.next/standalone/src/app/api/files/route.ts +187 -187
  622. package/.next/standalone/src/app/api/folders/route.ts +107 -97
  623. package/.next/standalone/src/app/api/network/connect-callback/route.ts +11 -11
  624. package/.next/standalone/src/app/api/network/connect-request/[id]/route.ts +11 -11
  625. package/.next/standalone/src/app/api/network/connect-request/route.ts +17 -17
  626. package/.next/standalone/src/app/api/network/discovered/route.ts +9 -9
  627. package/.next/standalone/src/app/api/network/handshake/route.ts +25 -25
  628. package/.next/standalone/src/app/api/network/health/route.ts +10 -10
  629. package/.next/standalone/src/app/api/network/identity/route.ts +15 -15
  630. package/.next/standalone/src/app/api/network/keys/[id]/route.ts +10 -10
  631. package/.next/standalone/src/app/api/network/keys/route.ts +15 -15
  632. package/.next/standalone/src/app/api/network/nodes/[id]/route.ts +15 -15
  633. package/.next/standalone/src/app/api/network/nodes/check/route.ts +9 -9
  634. package/.next/standalone/src/app/api/network/nodes/route.ts +15 -15
  635. package/.next/standalone/src/app/api/network/panes/[id]/route.ts +78 -62
  636. package/.next/standalone/src/app/api/network/panes/route.ts +61 -50
  637. package/.next/standalone/src/app/api/network/projects/route.ts +32 -25
  638. package/.next/standalone/src/app/api/network/proxy/[nodeId]/[...path]/route.ts +25 -25
  639. package/.next/standalone/src/app/api/network/search/route.ts +45 -38
  640. package/.next/standalone/src/app/api/network/sessions/[id]/messages/route.ts +43 -36
  641. package/.next/standalone/src/app/api/network/sessions/[id]/route.ts +41 -34
  642. package/.next/standalone/src/app/api/network/sessions/route.ts +50 -43
  643. package/.next/standalone/src/app/api/network/terminal/token/route.ts +10 -10
  644. package/.next/standalone/src/app/api/network/workspaces/[id]/route.ts +80 -71
  645. package/.next/standalone/src/app/api/network/workspaces/route.ts +87 -85
  646. package/.next/standalone/src/app/api/panes/[id]/diff/route.ts +121 -121
  647. package/.next/standalone/src/app/api/panes/[id]/route.ts +60 -60
  648. package/.next/standalone/src/app/api/panes/route.ts +39 -39
  649. package/.next/standalone/src/app/api/projects/route.ts +13 -13
  650. package/.next/standalone/src/app/api/proxy/models/[modelId]/[...path]/route.ts +80 -80
  651. package/.next/standalone/src/app/api/proxy/models/[modelId]/status/route.ts +33 -33
  652. package/.next/standalone/src/app/api/search/route.ts +47 -47
  653. package/.next/standalone/src/app/api/sessions/[id]/chat/route.ts +120 -120
  654. package/.next/standalone/src/app/api/sessions/[id]/messages/route.ts +34 -34
  655. package/.next/standalone/src/app/api/sessions/[id]/route.ts +73 -73
  656. package/.next/standalone/src/app/api/sessions/route.ts +64 -64
  657. package/.next/standalone/src/app/api/sync/route.ts +24 -24
  658. package/.next/standalone/src/app/api/tags/route.ts +35 -35
  659. package/.next/standalone/src/app/api/tier/route.ts +16 -16
  660. package/.next/standalone/src/app/api/updates/route.ts +65 -65
  661. package/.next/standalone/src/app/api/whisper/config/route.ts +50 -42
  662. package/.next/standalone/src/app/api/whisper/route.ts +91 -91
  663. package/.next/standalone/src/app/api/wizard/chart/route.ts +129 -0
  664. package/.next/standalone/src/app/api/wizard/chat/route.ts +113 -113
  665. package/.next/standalone/src/app/api/workspaces/[id]/context/[key]/route.ts +39 -39
  666. package/.next/standalone/src/app/api/workspaces/[id]/context/route.ts +28 -28
  667. package/.next/standalone/src/app/api/workspaces/[id]/messages/[msgId]/route.ts +17 -17
  668. package/.next/standalone/src/app/api/workspaces/[id]/messages/route.ts +39 -39
  669. package/.next/standalone/src/app/api/workspaces/[id]/route.ts +47 -47
  670. package/.next/standalone/src/app/api/workspaces/[id]/sessions/route.ts +62 -62
  671. package/.next/standalone/src/app/api/workspaces/route.ts +79 -79
  672. package/.next/standalone/src/app/globals.css +88 -88
  673. package/.next/standalone/src/app/layout.tsx +33 -33
  674. package/.next/standalone/src/app/login/layout.tsx +7 -7
  675. package/.next/standalone/src/app/login/page.tsx +315 -315
  676. package/.next/standalone/src/app/m/layout.tsx +16 -16
  677. package/.next/standalone/src/app/m/page.tsx +118 -118
  678. package/.next/standalone/src/app/m/projects/page.tsx +64 -64
  679. package/.next/standalone/src/app/m/sessions/[id]/page.tsx +168 -168
  680. package/.next/standalone/src/app/m/sessions/page.tsx +177 -177
  681. package/.next/standalone/src/app/m/settings/page.tsx +230 -230
  682. package/.next/standalone/src/app/m/terminal/page.tsx +413 -413
  683. package/.next/standalone/src/app/vr/page.tsx +21 -21
  684. package/.next/standalone/src/app/vr/vr-app.tsx +163 -163
  685. package/.next/standalone/src/app/vr/vr-controls.tsx +139 -139
  686. package/.next/standalone/src/app/vr/vr-door.tsx +82 -82
  687. package/.next/standalone/src/app/vr/vr-environment.tsx +71 -71
  688. package/.next/standalone/src/app/vr/vr-gaze.tsx +89 -89
  689. package/.next/standalone/src/app/vr/vr-layout.ts +49 -49
  690. package/.next/standalone/src/app/vr/vr-lobby.tsx +97 -97
  691. package/.next/standalone/src/app/vr/vr-pane.tsx +195 -195
  692. package/.next/standalone/src/app/vr/vr-room.tsx +79 -79
  693. package/.next/standalone/src/app/vr/vr-terminal.tsx +303 -303
  694. package/.next/standalone/src/components/auth/totp-gate.tsx +183 -183
  695. package/.next/standalone/src/components/bus/activity-panel.tsx +261 -261
  696. package/.next/standalone/src/components/common/color-picker.tsx +35 -35
  697. package/.next/standalone/src/components/common/dev-directory-picker.tsx +339 -339
  698. package/.next/standalone/src/components/common/folder-picker.tsx +200 -200
  699. package/.next/standalone/src/components/common/tag-picker.tsx +190 -190
  700. package/.next/standalone/src/components/common/workspace-picker.tsx +113 -113
  701. package/.next/standalone/src/components/cortex/benchmark-tab.tsx +894 -880
  702. package/.next/standalone/src/components/cortex/constants.ts +29 -29
  703. package/.next/standalone/src/components/cortex/cortex-dashboard.tsx +304 -304
  704. package/.next/standalone/src/components/cortex/cortex-indicator.tsx +44 -44
  705. package/.next/standalone/src/components/cortex/cortex-panel.tsx +140 -140
  706. package/.next/standalone/src/components/cortex/cortex-settings.tsx +280 -280
  707. package/.next/standalone/src/components/cortex/curation-tab.tsx +810 -810
  708. package/.next/standalone/src/components/cortex/entity-detail.tsx +101 -101
  709. package/.next/standalone/src/components/cortex/entity-graph.tsx +382 -382
  710. package/.next/standalone/src/components/cortex/import-dialog.tsx +212 -212
  711. package/.next/standalone/src/components/cortex/injection-badge.tsx +72 -72
  712. package/.next/standalone/src/components/cortex/knowledge-card.tsx +109 -109
  713. package/.next/standalone/src/components/cortex/knowledge-tab.tsx +158 -158
  714. package/.next/standalone/src/components/cortex/lobe-settings.tsx +215 -215
  715. package/.next/standalone/src/components/cortex/marketplace-card.tsx +126 -126
  716. package/.next/standalone/src/components/cortex/marketplace-tab.tsx +113 -113
  717. package/.next/standalone/src/components/dashboard/activity-chart.tsx +41 -41
  718. package/.next/standalone/src/components/dashboard/model-usage-chart.tsx +61 -61
  719. package/.next/standalone/src/components/dashboard/recent-sessions.tsx +68 -68
  720. package/.next/standalone/src/components/dashboard/stats-cards.tsx +36 -36
  721. package/.next/standalone/src/components/files/file-explorer.tsx +703 -703
  722. package/.next/standalone/src/components/layout/providers.tsx +38 -38
  723. package/.next/standalone/src/components/layout/sidebar.tsx +170 -170
  724. package/.next/standalone/src/components/layout/tier-provider.tsx +53 -53
  725. package/.next/standalone/src/components/layout/update-banner.tsx +92 -92
  726. package/.next/standalone/src/components/mobile/bottom-nav.tsx +46 -46
  727. package/.next/standalone/src/components/mobile/immersive-voice-button.tsx +123 -123
  728. package/.next/standalone/src/components/mobile/mobile-chat-input.tsx +244 -244
  729. package/.next/standalone/src/components/mobile/mobile-header.tsx +44 -44
  730. package/.next/standalone/src/components/mobile/mobile-session-card.tsx +56 -56
  731. package/.next/standalone/src/components/mobile/mobile-terminal-input.tsx +74 -74
  732. package/.next/standalone/src/components/mobile/mobile-terminal-pane.tsx +302 -302
  733. package/.next/standalone/src/components/mobile/mobile-terminal-toolbar.tsx +76 -76
  734. package/.next/standalone/src/components/mobile/pull-to-refresh.tsx +82 -82
  735. package/.next/standalone/src/components/mobile/voice-input.tsx +53 -53
  736. package/.next/standalone/src/components/network/api-key-list.tsx +190 -190
  737. package/.next/standalone/src/components/network/connection-requests.tsx +94 -94
  738. package/.next/standalone/src/components/network/node-add-dialog.tsx +131 -131
  739. package/.next/standalone/src/components/network/node-badge.tsx +26 -26
  740. package/.next/standalone/src/components/network/node-list.tsx +207 -207
  741. package/.next/standalone/src/components/network/node-selector.tsx +49 -49
  742. package/.next/standalone/src/components/sessions/session-filters.tsx +116 -116
  743. package/.next/standalone/src/components/sessions/session-list.tsx +485 -485
  744. package/.next/standalone/src/components/terminal/pane-diff-panel.tsx +179 -179
  745. package/.next/standalone/src/components/terminal/terminal-pane.tsx +1530 -1464
  746. package/.next/standalone/src/components/viewer/chat-input.tsx +275 -275
  747. package/.next/standalone/src/components/viewer/message-renderer.tsx +551 -551
  748. package/.next/standalone/src/components/wizard/chart-wizard.tsx +405 -0
  749. package/.next/standalone/src/components/wizard/project-wizard.tsx +153 -153
  750. package/.next/standalone/src/components/wizard/wizard-chat.tsx +99 -99
  751. package/.next/standalone/src/components/wizard/wizard-plan-summary.tsx +103 -103
  752. package/.next/standalone/src/components/wizard/wizard-review.tsx +225 -225
  753. package/.next/standalone/src/components/workspace/universe-cluster.tsx +131 -131
  754. package/.next/standalone/src/components/workspace/universe-orb.tsx +128 -128
  755. package/.next/standalone/src/components/workspace/universe-types.ts +22 -22
  756. package/.next/standalone/src/components/workspace/universe-utils.ts +11 -11
  757. package/.next/standalone/src/components/workspace/universe-view.tsx +397 -397
  758. package/.next/standalone/src/components/workspace/workspace-chooser.tsx +634 -634
  759. package/.next/standalone/src/hooks/use-benchmark.ts +72 -71
  760. package/.next/standalone/src/hooks/use-bus.ts +147 -147
  761. package/.next/standalone/src/hooks/use-idle-detection.ts +79 -79
  762. package/.next/standalone/src/hooks/use-network.ts +229 -229
  763. package/.next/standalone/src/hooks/use-sessions.ts +437 -437
  764. package/.next/standalone/src/hooks/use-speech-recognition.ts +114 -113
  765. package/.next/standalone/src/hooks/use-sse.ts +35 -35
  766. package/.next/standalone/src/hooks/use-tier.ts +39 -39
  767. package/.next/standalone/src/lib/agents.ts +97 -97
  768. package/.next/standalone/src/lib/aider/parser.ts +111 -111
  769. package/.next/standalone/src/lib/api.ts +19 -19
  770. package/.next/standalone/src/lib/auth.ts +47 -47
  771. package/.next/standalone/src/lib/claude/parser.ts +212 -212
  772. package/.next/standalone/src/lib/claude/stats.ts +204 -204
  773. package/.next/standalone/src/lib/codex/parser.test.ts +111 -111
  774. package/.next/standalone/src/lib/codex/parser.ts +287 -287
  775. package/.next/standalone/src/lib/config.ts +132 -132
  776. package/.next/standalone/src/lib/cortex/benchmark.ts +83 -67
  777. package/.next/standalone/src/lib/cortex/config.ts +42 -42
  778. package/.next/standalone/src/lib/cortex/debug.ts +10 -10
  779. package/.next/standalone/src/lib/cortex/distillation/usage-store.ts +18 -18
  780. package/.next/standalone/src/lib/cortex/graph/resolver.ts +10 -10
  781. package/.next/standalone/src/lib/cortex/graph/types.ts +22 -22
  782. package/.next/standalone/src/lib/cortex/index.ts +109 -56
  783. package/.next/standalone/src/lib/cortex/ingestion/bootstrap.ts +14 -14
  784. package/.next/standalone/src/lib/cortex/knowledge/compat.ts +14 -14
  785. package/.next/standalone/src/lib/cortex/knowledge/contradiction.ts +10 -10
  786. package/.next/standalone/src/lib/cortex/knowledge/types.ts +67 -67
  787. package/.next/standalone/src/lib/cortex/lobes/config.ts +16 -16
  788. package/.next/standalone/src/lib/cortex/lobes/resolver.ts +8 -8
  789. package/.next/standalone/src/lib/cortex/lobes/shares.ts +14 -14
  790. package/.next/standalone/src/lib/cortex/mcp/server.ts +12 -12
  791. package/.next/standalone/src/lib/cortex/portability/exporter.ts +6 -6
  792. package/.next/standalone/src/lib/cortex/portability/importer.ts +10 -10
  793. package/.next/standalone/src/lib/cortex/retrieval/context-engine.ts +10 -10
  794. package/.next/standalone/src/lib/cortex/types.ts +39 -39
  795. package/.next/standalone/src/lib/cost-calculator.ts +48 -48
  796. package/.next/standalone/src/lib/db/init.ts +71 -71
  797. package/.next/standalone/src/lib/db/queries.ts +740 -827
  798. package/.next/standalone/src/lib/db/schema.ts +206 -206
  799. package/.next/standalone/src/lib/events/sse.ts +36 -36
  800. package/.next/standalone/src/lib/forge/parser.ts +52 -52
  801. package/.next/standalone/src/lib/gemini/parser.ts +258 -258
  802. package/.next/standalone/src/lib/license.ts +56 -56
  803. package/.next/standalone/src/lib/pro.ts +31 -31
  804. package/.next/standalone/src/lib/shell-user.ts +101 -0
  805. package/.next/standalone/src/lib/sync/indexer.ts +504 -504
  806. package/.next/standalone/src/lib/sync/watcher.ts +64 -64
  807. package/.next/standalone/src/lib/teams.ts +31 -31
  808. package/.next/standalone/src/lib/telemetry.ts +75 -75
  809. package/.next/standalone/src/lib/terminal/server.ts +188 -188
  810. package/.next/standalone/src/lib/tier.ts +38 -38
  811. package/.next/standalone/src/lib/utils.ts +72 -72
  812. package/.next/standalone/src/lib/vms/manager.ts +121 -121
  813. package/.next/standalone/src/middleware.ts +133 -133
  814. package/.next/standalone/src/types/claude.ts +208 -208
  815. package/.next/standalone/src/types/network.ts +61 -61
  816. package/.next/standalone/tests/setup.ts +8 -8
  817. package/.next/standalone/tsconfig.json +34 -34
  818. package/.next/standalone/vitest.config.ts +24 -24
  819. package/LICENSE +661 -661
  820. package/README.md +131 -131
  821. package/bin/cortex-hook.js +79 -79
  822. package/bin/cortex-hook.sh +62 -62
  823. package/bin/cortex-learn-hook.js +138 -138
  824. package/bin/cortex-mcp.js +60 -60
  825. package/bin/cortex-pi-extension.ts +170 -170
  826. package/bin/fix-standalone-externals.js +79 -79
  827. package/bin/lib/auto-setup.js +110 -110
  828. package/bin/mdns-service.js +171 -171
  829. package/bin/postinstall.js +35 -35
  830. package/bin/setup-admin.js +195 -195
  831. package/bin/spaces-dev.js +247 -247
  832. package/bin/spaces-install.js +660 -660
  833. package/bin/spaces-postinstall.js +50 -50
  834. package/bin/spaces-reset-totp.js +50 -50
  835. package/bin/spaces-service.js +1046 -1046
  836. package/bin/spaces-setup.js +253 -253
  837. package/bin/spaces.js +808 -805
  838. package/bin/ssh-auth-keys.sh +68 -68
  839. package/bin/terminal-server.js +2819 -2781
  840. package/package.json +111 -111
  841. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0903a426._.js +0 -98
  842. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09e8ccc9._.js +0 -98
  843. package/.next/standalone/.next/server/chunks/[root-of-the-server]__11c684b1._.js +0 -98
  844. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1572d4ef._.js +0 -98
  845. package/.next/standalone/.next/server/chunks/[root-of-the-server]__186cd0bb._.js +0 -3
  846. package/.next/standalone/.next/server/chunks/[root-of-the-server]__212760e6._.js +0 -98
  847. package/.next/standalone/.next/server/chunks/[root-of-the-server]__228595ec._.js +0 -98
  848. package/.next/standalone/.next/server/chunks/[root-of-the-server]__283c890f._.js +0 -3
  849. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f300a68._.js +0 -98
  850. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f452778._.js +0 -98
  851. package/.next/standalone/.next/server/chunks/[root-of-the-server]__35f8e77e._.js +0 -98
  852. package/.next/standalone/.next/server/chunks/[root-of-the-server]__379fc2e9._.js +0 -98
  853. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3b40d79f._.js +0 -98
  854. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3d3dca2b._.js +0 -98
  855. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4d5b78d2._.js +0 -98
  856. package/.next/standalone/.next/server/chunks/[root-of-the-server]__54163e52._.js +0 -98
  857. package/.next/standalone/.next/server/chunks/[root-of-the-server]__563c0817._.js +0 -3
  858. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5812f90a._.js +0 -98
  859. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c5e87f5._.js +0 -98
  860. package/.next/standalone/.next/server/chunks/[root-of-the-server]__60d15b16._.js +0 -98
  861. package/.next/standalone/.next/server/chunks/[root-of-the-server]__69d315e5._.js +0 -3
  862. package/.next/standalone/.next/server/chunks/[root-of-the-server]__71f29038._.js +0 -98
  863. package/.next/standalone/.next/server/chunks/[root-of-the-server]__74084e07._.js +0 -3
  864. package/.next/standalone/.next/server/chunks/[root-of-the-server]__7921aa80._.js +0 -98
  865. package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e077dd8._.js +0 -98
  866. package/.next/standalone/.next/server/chunks/[root-of-the-server]__7ebc4280._.js +0 -131
  867. package/.next/standalone/.next/server/chunks/[root-of-the-server]__857c60bb._.js +0 -98
  868. package/.next/standalone/.next/server/chunks/[root-of-the-server]__874fe565._.js +0 -98
  869. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8e2171f7._.js +0 -98
  870. package/.next/standalone/.next/server/chunks/[root-of-the-server]__95659b2d._.js +0 -98
  871. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9679b91e._.js +0 -98
  872. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a90729a1._.js +0 -98
  873. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad4346fa._.js +0 -98
  874. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b0862d69._.js +0 -98
  875. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b43306ee._.js +0 -98
  876. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b689ff5e._.js +0 -106
  877. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ba87daaa._.js +0 -98
  878. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c0461005._.js +0 -98
  879. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c1deb5f3._.js +0 -98
  880. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c8a62f42._.js +0 -98
  881. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cabaac2b._.js +0 -98
  882. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cb027619._.js +0 -98
  883. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf608218._.js +0 -98
  884. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cfc1290d._.js +0 -98
  885. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d0109b9b._.js +0 -98
  886. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d0125483._.js +0 -3
  887. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d048ee6b._.js +0 -98
  888. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d12644e7._.js +0 -98
  889. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e3cc946c._.js +0 -98
  890. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e6fd27f8._.js +0 -98
  891. package/.next/standalone/.next/server/chunks/[root-of-the-server]__efb8251e._.js +0 -98
  892. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f44c6882._.js +0 -98
  893. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f85283de._.js +0 -98
  894. package/.next/standalone/.next/server/chunks/[root-of-the-server]__feceb3e4._.js +0 -98
  895. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__843070a6._.js +0 -3
  896. package/.next/standalone/.next/server/chunks/ssr/_e84a0c06._.js +0 -3
  897. package/.next/standalone/.next/static/chunks/470cade58d4eceeb.css +0 -3
  898. package/.next/standalone/.next/static/chunks/9d4164833c2c1fd6.js +0 -85
  899. package/.next/standalone/.next/static/chunks/f091f4bf8d80fd07.js +0 -1
  900. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
  901. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  902. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
  903. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  904. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
  905. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  906. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  907. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  908. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  909. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  910. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  911. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  912. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  913. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  914. /package/.next/standalone/.next/static/{5S4TviTCiNiTjf6KjXjBo → u1pHON3drz1mBi7owkbBP}/_buildManifest.js +0 -0
  915. /package/.next/standalone/.next/static/{5S4TviTCiNiTjf6KjXjBo → u1pHON3drz1mBi7owkbBP}/_clientMiddlewareManifest.json +0 -0
  916. /package/.next/standalone/.next/static/{5S4TviTCiNiTjf6KjXjBo → u1pHON3drz1mBi7owkbBP}/_ssgManifest.js +0 -0
  917. /package/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-win32-x64}/versions.json +0 -0
@@ -1,1639 +1,1639 @@
1
- # Spaces VR Phase 1: Shell + Static Rooms — Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Build a native Meta Quest 3 VR app that connects to a running Spaces server and renders workspace rooms with pane placeholders, navigable via eye tracking and hand tracking.
6
-
7
- **Architecture:** Unity project (`C:\projects\spaces-vr`) with Meta XR SDK. HTTP client polls Spaces server API. Two scenes: Lobby (doors per workspace) and WorkspaceRoom (pane placeholders in semicircle). Eye tracking for gaze focus, hand tracking for grab/move/resize.
8
-
9
- **Tech Stack:** Unity 2022.3 LTS, C#, Meta XR SDK v68+, Meta Interaction SDK, TextMeshPro, UnityWebRequest
10
-
11
- **Spec:** `docs/superpowers/specs/2026-03-19-vr-phase1-shell-design.md`
12
-
13
- **Important:** This is a Unity project. Many steps involve Unity Editor operations (creating scenes, configuring components, building prefabs) that cannot be fully automated via code. The plan provides exact C# scripts and documents which Editor steps are needed.
14
-
15
- ---
16
-
17
- ### Task 0: Server-Side — Add workspace_id Filter to Panes API
18
-
19
- **Context:** The VR client needs to fetch panes for a specific workspace. The current `GET /api/panes` route only returns panes for the active workspace. We need to support `?workspace_id=N`.
20
-
21
- **Files:**
22
- - Modify: `C:\projects\spaces\src\app\api\panes\route.ts`
23
-
24
- - [ ] **Step 1: Update the GET handler to accept workspace_id query param**
25
-
26
- Replace the GET handler in `src/app/api/panes/route.ts`:
27
-
28
- ```typescript
29
- export async function GET(request: NextRequest) {
30
- const user = getAuthUser(request);
31
- return withUser(user, async () => {
32
- await ensureInitialized();
33
- const url = new URL(request.url);
34
- const workspaceId = url.searchParams.get('workspace_id');
35
- if (workspaceId) {
36
- const { getPanesByWorkspace } = await import('@/lib/db/queries');
37
- return NextResponse.json(getPanesByWorkspace(parseInt(workspaceId, 10)));
38
- }
39
- return NextResponse.json(getActivePanes());
40
- });
41
- }
42
- ```
43
-
44
- Add `getPanesByWorkspace` to the imports if not already imported:
45
- ```typescript
46
- import { getActivePanes, createPane, getPanesByWorkspace } from '@/lib/db/queries';
47
- ```
48
-
49
- - [ ] **Step 2: Verify the change**
50
-
51
- Run: `npx tsc --noEmit 2>&1 | head -5`
52
- Expected: No errors
53
-
54
- - [ ] **Step 3: Commit**
55
-
56
- ```bash
57
- cd C:\projects\spaces
58
- git add src/app/api/panes/route.ts
59
- git commit -m "feat: add workspace_id filter to GET /api/panes for VR client"
60
- ```
61
-
62
- ---
63
-
64
- ### Task 1: Unity Project Setup
65
-
66
- **Context:** Create the Unity project and configure it for Quest 3 development. This task is mostly Unity Editor work.
67
-
68
- - [ ] **Step 1: Create the Unity project**
69
-
70
- Open Unity Hub → New Project → 3D (URP) template → Project name: `spaces-vr` → Location: `C:\projects\` → Create Project
71
-
72
- - [ ] **Step 2: Initialize git**
73
-
74
- ```bash
75
- cd C:\projects\spaces-vr
76
- git init
77
- ```
78
-
79
- Create `.gitignore`:
80
-
81
- ```
82
- # Unity
83
- [Ll]ibrary/
84
- [Tt]emp/
85
- [Oo]bj/
86
- [Bb]uild/
87
- [Bb]uilds/
88
- [Ll]ogs/
89
- [Uu]ser[Ss]ettings/
90
- *.csproj
91
- *.unityproj
92
- *.sln
93
- *.suo
94
- *.tmp
95
- *.user
96
- *.userprefs
97
- *.pidb
98
- *.booproj
99
- *.svd
100
- *.pdb
101
- *.mdb
102
- *.opendb
103
- *.VC.db
104
- *.pidb.meta
105
- *.pdb.meta
106
- *.mdb.meta
107
- crashlytics-buildid.txt
108
- sysinfo.txt
109
- *.apk
110
- *.aab
111
- *.unitypackage
112
- *.unitypackage.meta
113
- ```
114
-
115
- ```bash
116
- git add .gitignore
117
- git commit -m "chore: initialize spaces-vr Unity project"
118
- ```
119
-
120
- - [ ] **Step 3: Configure build settings for Quest 3**
121
-
122
- In Unity Editor:
123
- 1. File → Build Settings → Switch Platform to **Android**
124
- 2. Player Settings:
125
- - Other Settings → Scripting Backend: **IL2CPP**
126
- - Other Settings → Target Architectures: check **ARM64** only
127
- - Other Settings → Minimum API Level: **Android 12.0 (API 32)**
128
- - Other Settings → Graphics APIs: **Vulkan** only (remove OpenGLES)
129
- - Company Name: `Spaces`
130
- - Product Name: `Spaces VR`
131
- - Package Name: `com.spaces.vr`
132
-
133
- - [ ] **Step 4: Install Meta XR SDK packages**
134
-
135
- In Unity: Window → Package Manager → + → Add by name:
136
- - `com.meta.xr.sdk.all` (Meta XR All-in-One SDK)
137
-
138
- This pulls in OVR, Interaction SDK, and all Quest features.
139
-
140
- After import, accept any "Fix All" prompts from the Meta Project Setup Tool.
141
-
142
- - [ ] **Step 5: Configure OVR settings**
143
-
144
- 1. In Hierarchy, delete the default Main Camera
145
- 2. Add: right-click → XR → OVR Camera Rig
146
- 3. On the OVRCameraRig, in OVRManager:
147
- - Target Devices: Quest 3
148
- - Tracking Origin Type: Floor Level
149
- - Hand Tracking Support: Controllers and Hands
150
- - Eye Tracking Support: Supported (check "Required" = false for fallback)
151
- 4. Edit → Project Settings → XR Plug-in Management → check **Oculus**
152
- 5. Edit → Project Settings → Meta XR → check: Eye Tracking, Hand Tracking
153
-
154
- - [ ] **Step 6: Create folder structure**
155
-
156
- In Unity Project window, create these folders:
157
- ```
158
- Assets/
159
- ├── Scenes/
160
- ├── Scripts/
161
- │ ├── Core/
162
- │ ├── Lobby/
163
- │ ├── Room/
164
- │ ├── Interaction/
165
- │ └── UI/
166
- ├── Prefabs/
167
- ├── Materials/
168
- └── Shaders/
169
- ```
170
-
171
- - [ ] **Step 7: Commit**
172
-
173
- ```bash
174
- cd C:\projects\spaces-vr
175
- git add -A
176
- git commit -m "feat: configure Unity project for Quest 3 with Meta XR SDK"
177
- ```
178
-
179
- ---
180
-
181
- ### Task 2: Data Models and Server Connection
182
-
183
- **Files:**
184
- - Create: `Assets/Scripts/Core/WorkspaceData.cs`
185
- - Create: `Assets/Scripts/Core/SpacesConnection.cs`
186
- - Create: `Assets/Scripts/Core/SessionManager.cs`
187
-
188
- - [ ] **Step 1: Create data models**
189
-
190
- ```csharp
191
- // Assets/Scripts/Core/WorkspaceData.cs
192
- using System;
193
- using System.Collections.Generic;
194
-
195
- [Serializable]
196
- public class WorkspaceData
197
- {
198
- public int id;
199
- public string name;
200
- public string color;
201
- public int paneCount;
202
- }
203
-
204
- [Serializable]
205
- public class PaneData
206
- {
207
- public string id;
208
- public string agentType;
209
- public string title;
210
- }
211
-
212
- [Serializable]
213
- public class WorkspaceListResponse
214
- {
215
- public List<WorkspaceData> items;
216
- }
217
-
218
- // Helper to deserialize JSON array (Unity's JsonUtility doesn't handle top-level arrays)
219
- public static class JsonArrayHelper
220
- {
221
- public static List<T> FromJson<T>(string json)
222
- {
223
- string wrapped = "{\"items\":" + json + "}";
224
- var wrapper = UnityEngine.JsonUtility.FromJson<Wrapper<T>>(wrapped);
225
- return wrapper.items;
226
- }
227
-
228
- [Serializable]
229
- private class Wrapper<T>
230
- {
231
- public List<T> items;
232
- }
233
- }
234
- ```
235
-
236
- - [ ] **Step 2: Create session manager**
237
-
238
- ```csharp
239
- // Assets/Scripts/Core/SessionManager.cs
240
- using UnityEngine;
241
-
242
- public class SessionManager : MonoBehaviour
243
- {
244
- public static SessionManager Instance { get; private set; }
245
-
246
- public string ServerUrl { get; set; } = "http://localhost:3457";
247
- public string SessionCookie { get; private set; }
248
- public bool IsAuthenticated => !string.IsNullOrEmpty(SessionCookie);
249
-
250
- private void Awake()
251
- {
252
- if (Instance != null) { Destroy(gameObject); return; }
253
- Instance = this;
254
- DontDestroyOnLoad(gameObject);
255
- }
256
-
257
- public void SetCookie(string cookie)
258
- {
259
- SessionCookie = cookie;
260
- }
261
-
262
- public void ClearSession()
263
- {
264
- SessionCookie = null;
265
- }
266
- }
267
- ```
268
-
269
- - [ ] **Step 3: Create HTTP connection client**
270
-
271
- ```csharp
272
- // Assets/Scripts/Core/SpacesConnection.cs
273
- using System;
274
- using System.Collections;
275
- using System.Collections.Generic;
276
- using UnityEngine;
277
- using UnityEngine.Networking;
278
-
279
- public class SpacesConnection : MonoBehaviour
280
- {
281
- public static SpacesConnection Instance { get; private set; }
282
-
283
- [SerializeField] private float workspacePollingInterval = 30f;
284
- [SerializeField] private float panePollingInterval = 10f;
285
- [SerializeField] private float requestTimeout = 5f;
286
- [SerializeField] private int maxConsecutiveFailures = 3;
287
-
288
- public event Action<List<WorkspaceData>> OnWorkspacesUpdated;
289
- public event Action<List<PaneData>> OnPanesUpdated;
290
- public event Action<bool> OnConnectionStateChanged;
291
-
292
- private int consecutiveFailures;
293
- private bool isConnected;
294
-
295
- private void Awake()
296
- {
297
- if (Instance != null) { Destroy(gameObject); return; }
298
- Instance = this;
299
- DontDestroyOnLoad(gameObject);
300
- }
301
-
302
- private string BaseUrl => SessionManager.Instance.ServerUrl;
303
-
304
- public IEnumerator FetchWorkspaces(Action<List<WorkspaceData>> callback)
305
- {
306
- using var req = UnityWebRequest.Get($"{BaseUrl}/api/workspaces");
307
- req.timeout = (int)requestTimeout;
308
- if (SessionManager.Instance.IsAuthenticated)
309
- req.SetRequestHeader("Cookie", SessionManager.Instance.SessionCookie);
310
-
311
- yield return req.SendWebRequest();
312
-
313
- if (req.result == UnityWebRequest.Result.Success)
314
- {
315
- consecutiveFailures = 0;
316
- if (!isConnected) { isConnected = true; OnConnectionStateChanged?.Invoke(true); }
317
-
318
- var workspaces = JsonArrayHelper.FromJson<WorkspaceData>(req.downloadHandler.text);
319
- callback?.Invoke(workspaces);
320
- OnWorkspacesUpdated?.Invoke(workspaces);
321
- }
322
- else
323
- {
324
- HandleFailure();
325
- callback?.Invoke(null);
326
- }
327
- }
328
-
329
- public IEnumerator FetchPanes(int workspaceId, Action<List<PaneData>> callback)
330
- {
331
- using var req = UnityWebRequest.Get($"{BaseUrl}/api/panes?workspace_id={workspaceId}");
332
- req.timeout = (int)requestTimeout;
333
- if (SessionManager.Instance.IsAuthenticated)
334
- req.SetRequestHeader("Cookie", SessionManager.Instance.SessionCookie);
335
-
336
- yield return req.SendWebRequest();
337
-
338
- if (req.result == UnityWebRequest.Result.Success)
339
- {
340
- consecutiveFailures = 0;
341
- var panes = JsonArrayHelper.FromJson<PaneData>(req.downloadHandler.text);
342
- callback?.Invoke(panes);
343
- OnPanesUpdated?.Invoke(panes);
344
- }
345
- else
346
- {
347
- HandleFailure();
348
- callback?.Invoke(null);
349
- }
350
- }
351
-
352
- public IEnumerator TryConnect(Action<bool> callback)
353
- {
354
- // Try unauthenticated first
355
- using var req = UnityWebRequest.Get($"{BaseUrl}/api/workspaces");
356
- req.timeout = (int)requestTimeout;
357
- yield return req.SendWebRequest();
358
-
359
- if (req.result == UnityWebRequest.Result.Success)
360
- {
361
- isConnected = true;
362
- OnConnectionStateChanged?.Invoke(true);
363
- callback?.Invoke(true);
364
- }
365
- else if (req.responseCode == 401)
366
- {
367
- // Auth required — need login UI (Phase 1: show message)
368
- Debug.Log("[SpacesVR] Server requires authentication");
369
- callback?.Invoke(false);
370
- }
371
- else
372
- {
373
- HandleFailure();
374
- callback?.Invoke(false);
375
- }
376
- }
377
-
378
- private void HandleFailure()
379
- {
380
- consecutiveFailures++;
381
- if (consecutiveFailures >= maxConsecutiveFailures && isConnected)
382
- {
383
- isConnected = false;
384
- OnConnectionStateChanged?.Invoke(false);
385
- }
386
- }
387
-
388
- // Coroutine-based polling helpers
389
- public IEnumerator PollWorkspaces()
390
- {
391
- while (true)
392
- {
393
- yield return FetchWorkspaces(null);
394
- yield return new WaitForSeconds(workspacePollingInterval);
395
- }
396
- }
397
-
398
- public IEnumerator PollPanes(int workspaceId)
399
- {
400
- while (true)
401
- {
402
- yield return FetchPanes(workspaceId, null);
403
- yield return new WaitForSeconds(panePollingInterval);
404
- }
405
- }
406
- }
407
- ```
408
-
409
- - [ ] **Step 4: Commit**
410
-
411
- ```bash
412
- cd C:\projects\spaces-vr
413
- git add Assets/Scripts/Core/
414
- git commit -m "feat: add data models and HTTP connection client"
415
- ```
416
-
417
- ---
418
-
419
- ### Task 3: Gaze Manager (Eye Tracking + Fallback)
420
-
421
- **Files:**
422
- - Create: `Assets/Scripts/Interaction/GazeManager.cs`
423
- - Create: `Assets/Scripts/Interaction/GazeFocusHighlight.cs`
424
-
425
- - [ ] **Step 1: Create GazeManager**
426
-
427
- ```csharp
428
- // Assets/Scripts/Interaction/GazeManager.cs
429
- using UnityEngine;
430
-
431
- public class GazeManager : MonoBehaviour
432
- {
433
- public static GazeManager Instance { get; private set; }
434
-
435
- [SerializeField] private float focusDelay = 0.3f; // 300ms sustained gaze
436
- [SerializeField] private float unfocusDelay = 0.2f;
437
- [SerializeField] private float maxRayDistance = 20f;
438
- [SerializeField] private LayerMask gazeLayerMask = ~0;
439
-
440
- public GameObject FocusedObject { get; private set; }
441
- public RaycastHit? LastHit { get; private set; }
442
- public event System.Action<GameObject> OnFocusChanged;
443
-
444
- private OVREyeGaze leftEyeGaze;
445
- private OVREyeGaze rightEyeGaze;
446
- private bool useEyeTracking;
447
- private Transform centerEye;
448
-
449
- private GameObject candidateObject;
450
- private float candidateTime;
451
- private float unfocusTime;
452
-
453
- private void Awake()
454
- {
455
- if (Instance != null) { Destroy(gameObject); return; }
456
- Instance = this;
457
- }
458
-
459
- private void Start()
460
- {
461
- // Try to find eye tracking components
462
- var eyeGazes = FindObjectsOfType<OVREyeGaze>();
463
- foreach (var eg in eyeGazes)
464
- {
465
- if (eg.Eye == OVREyeGaze.EyeId.Combined || eg.Eye == OVREyeGaze.EyeId.Left)
466
- leftEyeGaze = eg;
467
- }
468
-
469
- // Find center eye anchor for fallback
470
- var rig = FindObjectOfType<OVRCameraRig>();
471
- if (rig != null) centerEye = rig.centerEyeAnchor;
472
-
473
- // Check if eye tracking is available
474
- useEyeTracking = OVRPlugin.eyeTrackingEnabled;
475
- if (!useEyeTracking)
476
- Debug.Log("[GazeManager] Eye tracking unavailable, using head-gaze fallback");
477
- }
478
-
479
- private void Update()
480
- {
481
- Ray gazeRay = GetGazeRay();
482
- RaycastHit hit;
483
- bool didHit = Physics.Raycast(gazeRay, out hit, maxRayDistance, gazeLayerMask);
484
-
485
- if (didHit)
486
- {
487
- LastHit = hit;
488
- var hitObj = hit.collider.gameObject;
489
-
490
- if (hitObj == FocusedObject)
491
- {
492
- // Still looking at focused object — reset unfocus timer
493
- unfocusTime = 0f;
494
- }
495
- else if (hitObj == candidateObject)
496
- {
497
- // Still looking at candidate — accumulate focus time
498
- candidateTime += Time.deltaTime;
499
- if (candidateTime >= focusDelay)
500
- {
501
- SetFocus(hitObj);
502
- }
503
- }
504
- else
505
- {
506
- // New candidate
507
- candidateObject = hitObj;
508
- candidateTime = 0f;
509
- }
510
- }
511
- else
512
- {
513
- LastHit = null;
514
- candidateObject = null;
515
- candidateTime = 0f;
516
-
517
- if (FocusedObject != null)
518
- {
519
- unfocusTime += Time.deltaTime;
520
- if (unfocusTime >= unfocusDelay)
521
- {
522
- SetFocus(null);
523
- }
524
- }
525
- }
526
- }
527
-
528
- private Ray GetGazeRay()
529
- {
530
- if (useEyeTracking && leftEyeGaze != null)
531
- {
532
- return new Ray(leftEyeGaze.transform.position, leftEyeGaze.transform.forward);
533
- }
534
-
535
- // Head-gaze fallback
536
- if (centerEye != null)
537
- {
538
- return new Ray(centerEye.position, centerEye.forward);
539
- }
540
-
541
- return new Ray(Camera.main.transform.position, Camera.main.transform.forward);
542
- }
543
-
544
- private void SetFocus(GameObject obj)
545
- {
546
- if (FocusedObject == obj) return;
547
-
548
- // Notify old object
549
- if (FocusedObject != null)
550
- {
551
- var highlight = FocusedObject.GetComponent<GazeFocusHighlight>();
552
- if (highlight != null) highlight.OnUnfocus();
553
- }
554
-
555
- FocusedObject = obj;
556
- unfocusTime = 0f;
557
- candidateObject = null;
558
- candidateTime = 0f;
559
-
560
- // Notify new object
561
- if (FocusedObject != null)
562
- {
563
- var highlight = FocusedObject.GetComponent<GazeFocusHighlight>();
564
- if (highlight != null) highlight.OnFocus();
565
- }
566
-
567
- OnFocusChanged?.Invoke(FocusedObject);
568
- }
569
- }
570
- ```
571
-
572
- - [ ] **Step 2: Create GazeFocusHighlight**
573
-
574
- ```csharp
575
- // Assets/Scripts/Interaction/GazeFocusHighlight.cs
576
- using UnityEngine;
577
-
578
- public class GazeFocusHighlight : MonoBehaviour
579
- {
580
- [SerializeField] private float highlightScale = 1.02f;
581
- [SerializeField] private float transitionSpeed = 8f;
582
- [SerializeField] private Color highlightColor = new Color(0.5f, 0.3f, 1f, 0.3f); // purple glow
583
-
584
- private Vector3 originalScale;
585
- private bool isFocused;
586
- private float focusLerp; // 0 = unfocused, 1 = focused
587
- private Renderer[] renderers;
588
- private MaterialPropertyBlock propBlock;
589
-
590
- private void Awake()
591
- {
592
- originalScale = transform.localScale;
593
- renderers = GetComponentsInChildren<Renderer>();
594
- propBlock = new MaterialPropertyBlock();
595
- }
596
-
597
- private void Update()
598
- {
599
- float target = isFocused ? 1f : 0f;
600
- focusLerp = Mathf.MoveTowards(focusLerp, target, Time.deltaTime * transitionSpeed);
601
-
602
- // Scale
603
- transform.localScale = Vector3.Lerp(originalScale, originalScale * highlightScale, focusLerp);
604
-
605
- // Emission glow
606
- foreach (var r in renderers)
607
- {
608
- r.GetPropertyBlock(propBlock);
609
- propBlock.SetColor("_EmissionColor", highlightColor * focusLerp);
610
- r.SetPropertyBlock(propBlock);
611
- }
612
- }
613
-
614
- public void OnFocus()
615
- {
616
- isFocused = true;
617
- }
618
-
619
- public void OnUnfocus()
620
- {
621
- isFocused = false;
622
- }
623
- }
624
- ```
625
-
626
- - [ ] **Step 3: Commit**
627
-
628
- ```bash
629
- cd C:\projects\spaces-vr
630
- git add Assets/Scripts/Interaction/GazeManager.cs Assets/Scripts/Interaction/GazeFocusHighlight.cs
631
- git commit -m "feat: add gaze manager with eye tracking and head-gaze fallback"
632
- ```
633
-
634
- ---
635
-
636
- ### Task 4: Pane Layout Math and Pane Surface
637
-
638
- **Files:**
639
- - Create: `Assets/Scripts/Room/PaneLayout.cs`
640
- - Create: `Assets/Scripts/Room/PaneSurface.cs`
641
- - Create: `Assets/Scripts/UI/PaneHeader.cs`
642
- - Create: `Assets/Scripts/UI/StatusIndicator.cs`
643
-
644
- - [ ] **Step 1: Create PaneLayout (semicircle positioning)**
645
-
646
- ```csharp
647
- // Assets/Scripts/Room/PaneLayout.cs
648
- using UnityEngine;
649
-
650
- public static class PaneLayout
651
- {
652
- public static Vector3[] ComputePositions(int paneCount, float radius = 2f, float eyeHeight = 1.6f)
653
- {
654
- if (paneCount <= 0) return new Vector3[0];
655
-
656
- var positions = new Vector3[paneCount];
657
-
658
- if (paneCount == 1)
659
- {
660
- positions[0] = new Vector3(0, eyeHeight, radius);
661
- return positions;
662
- }
663
-
664
- // Arc grows with pane count
665
- float totalArcDeg;
666
- if (paneCount <= 2) totalArcDeg = 40f;
667
- else if (paneCount <= 4) totalArcDeg = 80f;
668
- else if (paneCount <= 6) totalArcDeg = 120f;
669
- else totalArcDeg = 160f;
670
-
671
- float startAngle = 90f - totalArcDeg / 2f; // center the arc at 90° (forward)
672
- float step = totalArcDeg / (paneCount - 1);
673
-
674
- for (int i = 0; i < paneCount; i++)
675
- {
676
- float angleDeg = startAngle + step * i;
677
- float angleRad = angleDeg * Mathf.Deg2Rad;
678
- float x = Mathf.Cos(angleRad) * radius;
679
- float z = Mathf.Sin(angleRad) * radius;
680
- positions[i] = new Vector3(x, eyeHeight, z);
681
- }
682
-
683
- return positions;
684
- }
685
-
686
- public static Quaternion ComputeRotation(Vector3 panePosition)
687
- {
688
- // Face inward toward room center (Y-axis billboard only)
689
- Vector3 toCenter = -new Vector3(panePosition.x, 0, panePosition.z).normalized;
690
- return Quaternion.LookRotation(toCenter, Vector3.up);
691
- }
692
- }
693
- ```
694
-
695
- - [ ] **Step 2: Create StatusIndicator**
696
-
697
- ```csharp
698
- // Assets/Scripts/UI/StatusIndicator.cs
699
- using UnityEngine;
700
-
701
- public enum PaneStatus
702
- {
703
- Idle, // gray
704
- Active, // green
705
- Waiting, // amber
706
- Error // red
707
- }
708
-
709
- public class StatusIndicator : MonoBehaviour
710
- {
711
- [SerializeField] private Renderer dotRenderer;
712
-
713
- private static readonly Color IdleColor = new Color(0.4f, 0.4f, 0.4f);
714
- private static readonly Color ActiveColor = new Color(0.2f, 0.8f, 0.2f);
715
- private static readonly Color WaitingColor = new Color(0.9f, 0.7f, 0.1f);
716
- private static readonly Color ErrorColor = new Color(0.9f, 0.2f, 0.2f);
717
-
718
- private MaterialPropertyBlock propBlock;
719
-
720
- private void Awake()
721
- {
722
- propBlock = new MaterialPropertyBlock();
723
- }
724
-
725
- public void SetStatus(PaneStatus status)
726
- {
727
- Color c = status switch
728
- {
729
- PaneStatus.Active => ActiveColor,
730
- PaneStatus.Waiting => WaitingColor,
731
- PaneStatus.Error => ErrorColor,
732
- _ => IdleColor,
733
- };
734
-
735
- if (dotRenderer != null)
736
- {
737
- dotRenderer.GetPropertyBlock(propBlock);
738
- propBlock.SetColor("_Color", c);
739
- propBlock.SetColor("_EmissionColor", c * 0.5f);
740
- dotRenderer.SetPropertyBlock(propBlock);
741
- }
742
- }
743
- }
744
- ```
745
-
746
- - [ ] **Step 3: Create PaneHeader**
747
-
748
- ```csharp
749
- // Assets/Scripts/UI/PaneHeader.cs
750
- using TMPro;
751
- using UnityEngine;
752
-
753
- public class PaneHeader : MonoBehaviour
754
- {
755
- [SerializeField] private TextMeshPro titleText;
756
- [SerializeField] private TextMeshPro subtitleText;
757
- [SerializeField] private Renderer accentBar;
758
-
759
- private MaterialPropertyBlock propBlock;
760
-
761
- private void Awake()
762
- {
763
- propBlock = new MaterialPropertyBlock();
764
- }
765
-
766
- public void SetInfo(string agentType, string title, Color workspaceColor)
767
- {
768
- if (titleText != null)
769
- titleText.text = string.IsNullOrEmpty(title) ? agentType : title;
770
-
771
- if (subtitleText != null)
772
- subtitleText.text = agentType;
773
-
774
- if (accentBar != null)
775
- {
776
- accentBar.GetPropertyBlock(propBlock);
777
- propBlock.SetColor("_Color", workspaceColor);
778
- accentBar.SetPropertyBlock(propBlock);
779
- }
780
- }
781
- }
782
- ```
783
-
784
- - [ ] **Step 4: Create PaneSurface**
785
-
786
- ```csharp
787
- // Assets/Scripts/Room/PaneSurface.cs
788
- using TMPro;
789
- using UnityEngine;
790
-
791
- [RequireComponent(typeof(BoxCollider))]
792
- [RequireComponent(typeof(GazeFocusHighlight))]
793
- public class PaneSurface : MonoBehaviour
794
- {
795
- [SerializeField] private TextMeshPro contentText;
796
- [SerializeField] private PaneHeader header;
797
- [SerializeField] private StatusIndicator statusIndicator;
798
-
799
- public string PaneId { get; private set; }
800
- public PaneData Data { get; private set; }
801
-
802
- private static readonly Vector3 DefaultSize = new Vector3(1.2f, 0.8f, 0.01f);
803
- private static readonly Vector3 MinSize = new Vector3(0.6f, 0.4f, 0.01f);
804
- private static readonly Vector3 MaxSize = new Vector3(2.4f, 1.6f, 0.01f);
805
-
806
- public void Initialize(PaneData data, Color workspaceColor)
807
- {
808
- Data = data;
809
- PaneId = data.id;
810
-
811
- header?.SetInfo(data.agentType, data.title, workspaceColor);
812
- statusIndicator?.SetStatus(PaneStatus.Idle); // Phase 1: always idle
813
-
814
- if (contentText != null)
815
- contentText.text = $"<color=#666>{data.agentType}\nConnecting...</color>";
816
- }
817
-
818
- public void SetSize(Vector3 newSize)
819
- {
820
- newSize.x = Mathf.Clamp(newSize.x, MinSize.x, MaxSize.x);
821
- newSize.y = Mathf.Clamp(newSize.y, MinSize.y, MaxSize.y);
822
- newSize.z = DefaultSize.z;
823
- transform.localScale = newSize;
824
- }
825
- }
826
- ```
827
-
828
- - [ ] **Step 5: Commit**
829
-
830
- ```bash
831
- cd C:\projects\spaces-vr
832
- git add Assets/Scripts/Room/ Assets/Scripts/UI/
833
- git commit -m "feat: add pane layout, surface, header, and status indicator"
834
- ```
835
-
836
- ---
837
-
838
- ### Task 5: Hand Tracking — Grab and Resize
839
-
840
- **Files:**
841
- - Create: `Assets/Scripts/Interaction/HandGrabHandler.cs`
842
-
843
- - [ ] **Step 1: Create HandGrabHandler**
844
-
845
- ```csharp
846
- // Assets/Scripts/Interaction/HandGrabHandler.cs
847
- using UnityEngine;
848
-
849
- public class HandGrabHandler : MonoBehaviour
850
- {
851
- [SerializeField] private OVRHand leftHand;
852
- [SerializeField] private OVRHand rightHand;
853
- [SerializeField] private float grabDistance = 0.15f;
854
- [SerializeField] private float smoothing = 15f;
855
- [SerializeField] private float minDistFromCenter = 1f;
856
- [SerializeField] private float maxDistFromCenter = 4f;
857
- [SerializeField] private float minHeight = 0.5f;
858
- [SerializeField] private float maxHeight = 3f;
859
-
860
- private PaneSurface grabbedPane;
861
- private Transform grabbingHand;
862
- private Vector3 grabOffset;
863
-
864
- // Two-hand resize state
865
- private bool isResizing;
866
- private float initialPinchDistance;
867
- private Vector3 initialPaneScale;
868
-
869
- private void Update()
870
- {
871
- bool leftPinch = leftHand != null && leftHand.GetFingerIsPinching(OVRHand.HandFinger.Index);
872
- bool rightPinch = rightHand != null && rightHand.GetFingerIsPinching(OVRHand.HandFinger.Index);
873
-
874
- // Two-hand resize
875
- if (leftPinch && rightPinch && grabbedPane != null)
876
- {
877
- HandleResize();
878
- return;
879
- }
880
-
881
- if (isResizing)
882
- {
883
- isResizing = false;
884
- }
885
-
886
- // Single-hand grab
887
- if (!leftPinch && !rightPinch)
888
- {
889
- grabbedPane = null;
890
- grabbingHand = null;
891
- return;
892
- }
893
-
894
- Transform activeHand = leftPinch ? leftHand.transform : rightHand.transform;
895
-
896
- if (grabbedPane == null)
897
- {
898
- // Try to grab
899
- TryGrab(activeHand);
900
- }
901
- else if (grabbingHand == activeHand)
902
- {
903
- // Move grabbed pane
904
- MoveGrabbedPane();
905
- }
906
- }
907
-
908
- private void TryGrab(Transform hand)
909
- {
910
- Collider[] hits = Physics.OverlapSphere(hand.position, grabDistance);
911
- foreach (var hit in hits)
912
- {
913
- var pane = hit.GetComponent<PaneSurface>();
914
- if (pane != null)
915
- {
916
- grabbedPane = pane;
917
- grabbingHand = hand;
918
- grabOffset = pane.transform.position - hand.position;
919
- return;
920
- }
921
- }
922
- }
923
-
924
- private void MoveGrabbedPane()
925
- {
926
- Vector3 targetPos = grabbingHand.position + grabOffset;
927
-
928
- // Constrain position
929
- float dist = new Vector2(targetPos.x, targetPos.z).magnitude;
930
- if (dist < minDistFromCenter || dist > maxDistFromCenter)
931
- {
932
- Vector2 dir = new Vector2(targetPos.x, targetPos.z).normalized;
933
- dist = Mathf.Clamp(dist, minDistFromCenter, maxDistFromCenter);
934
- targetPos.x = dir.x * dist;
935
- targetPos.z = dir.y * dist;
936
- }
937
- targetPos.y = Mathf.Clamp(targetPos.y, minHeight, maxHeight);
938
-
939
- grabbedPane.transform.position = Vector3.Lerp(
940
- grabbedPane.transform.position, targetPos, Time.deltaTime * smoothing);
941
-
942
- // Re-orient to face center
943
- grabbedPane.transform.rotation = PaneLayout.ComputeRotation(grabbedPane.transform.position);
944
- }
945
-
946
- private void HandleResize()
947
- {
948
- float currentDist = Vector3.Distance(leftHand.transform.position, rightHand.transform.position);
949
-
950
- if (!isResizing)
951
- {
952
- isResizing = true;
953
- initialPinchDistance = currentDist;
954
- initialPaneScale = grabbedPane.transform.localScale;
955
- return;
956
- }
957
-
958
- float scaleFactor = currentDist / initialPinchDistance;
959
- Vector3 newScale = initialPaneScale * scaleFactor;
960
- grabbedPane.SetSize(newScale);
961
- }
962
- }
963
- ```
964
-
965
- - [ ] **Step 2: Commit**
966
-
967
- ```bash
968
- cd C:\projects\spaces-vr
969
- git add Assets/Scripts/Interaction/HandGrabHandler.cs
970
- git commit -m "feat: add hand tracking grab-to-move and pinch-to-resize"
971
- ```
972
-
973
- ---
974
-
975
- ### Task 6: Lobby Scene — Door Spawning and Navigation
976
-
977
- **Files:**
978
- - Create: `Assets/Scripts/Lobby/LobbyManager.cs`
979
- - Create: `Assets/Scripts/Lobby/WorkspaceDoor.cs`
980
- - Create: `Assets/Scripts/Lobby/DoorInteraction.cs`
981
-
982
- - [ ] **Step 1: Create WorkspaceDoor**
983
-
984
- ```csharp
985
- // Assets/Scripts/Lobby/WorkspaceDoor.cs
986
- using TMPro;
987
- using UnityEngine;
988
-
989
- public class WorkspaceDoor : MonoBehaviour
990
- {
991
- [SerializeField] private TextMeshPro nameLabel;
992
- [SerializeField] private TextMeshPro infoLabel;
993
- [SerializeField] private Renderer frameRenderer;
994
- [SerializeField] private Renderer glowRenderer;
995
-
996
- public WorkspaceData Data { get; private set; }
997
-
998
- private MaterialPropertyBlock propBlock;
999
-
1000
- private void Awake()
1001
- {
1002
- propBlock = new MaterialPropertyBlock();
1003
- }
1004
-
1005
- public void Initialize(WorkspaceData data)
1006
- {
1007
- Data = data;
1008
-
1009
- if (nameLabel != null) nameLabel.text = data.name;
1010
- if (infoLabel != null) infoLabel.text = $"{data.paneCount} pane{(data.paneCount != 1 ? "s" : "")}";
1011
-
1012
- Color wsColor;
1013
- if (!ColorUtility.TryParseHtmlString(data.color, out wsColor))
1014
- wsColor = new Color(0.4f, 0.3f, 0.9f); // default purple
1015
-
1016
- if (frameRenderer != null)
1017
- {
1018
- frameRenderer.GetPropertyBlock(propBlock);
1019
- propBlock.SetColor("_Color", wsColor);
1020
- frameRenderer.SetPropertyBlock(propBlock);
1021
- }
1022
-
1023
- if (glowRenderer != null)
1024
- {
1025
- glowRenderer.GetPropertyBlock(propBlock);
1026
- propBlock.SetColor("_EmissionColor", wsColor * 0.3f);
1027
- glowRenderer.SetPropertyBlock(propBlock);
1028
- }
1029
- }
1030
- }
1031
- ```
1032
-
1033
- - [ ] **Step 2: Create DoorInteraction (gaze-to-enter)**
1034
-
1035
- ```csharp
1036
- // Assets/Scripts/Lobby/DoorInteraction.cs
1037
- using UnityEngine;
1038
-
1039
- [RequireComponent(typeof(GazeFocusHighlight))]
1040
- public class DoorInteraction : MonoBehaviour
1041
- {
1042
- [SerializeField] private float confirmTime = 1.5f;
1043
- [SerializeField] private Renderer confirmRing; // a progress ring UI element
1044
-
1045
- private float gazeTimer;
1046
- private bool isGazed;
1047
- private WorkspaceDoor door;
1048
- private MaterialPropertyBlock propBlock;
1049
-
1050
- private void Awake()
1051
- {
1052
- door = GetComponent<WorkspaceDoor>();
1053
- propBlock = new MaterialPropertyBlock();
1054
- }
1055
-
1056
- private void OnEnable()
1057
- {
1058
- if (GazeManager.Instance != null)
1059
- GazeManager.Instance.OnFocusChanged += HandleFocusChanged;
1060
- }
1061
-
1062
- private void OnDisable()
1063
- {
1064
- if (GazeManager.Instance != null)
1065
- GazeManager.Instance.OnFocusChanged -= HandleFocusChanged;
1066
- }
1067
-
1068
- private void HandleFocusChanged(GameObject focused)
1069
- {
1070
- isGazed = (focused == gameObject);
1071
- if (!isGazed)
1072
- {
1073
- gazeTimer = 0f;
1074
- UpdateRing(0f);
1075
- }
1076
- }
1077
-
1078
- private void Update()
1079
- {
1080
- if (!isGazed) return;
1081
-
1082
- gazeTimer += Time.deltaTime;
1083
- UpdateRing(gazeTimer / confirmTime);
1084
-
1085
- if (gazeTimer >= confirmTime)
1086
- {
1087
- gazeTimer = 0f;
1088
- EnterWorkspace();
1089
- }
1090
- }
1091
-
1092
- private void UpdateRing(float progress)
1093
- {
1094
- if (confirmRing != null)
1095
- {
1096
- confirmRing.gameObject.SetActive(progress > 0f);
1097
- confirmRing.GetPropertyBlock(propBlock);
1098
- propBlock.SetFloat("_Progress", Mathf.Clamp01(progress));
1099
- confirmRing.SetPropertyBlock(propBlock);
1100
- }
1101
- }
1102
-
1103
- private void EnterWorkspace()
1104
- {
1105
- if (door?.Data != null)
1106
- {
1107
- LobbyManager.Instance?.EnterWorkspace(door.Data);
1108
- }
1109
- }
1110
- }
1111
- ```
1112
-
1113
- - [ ] **Step 3: Create LobbyManager**
1114
-
1115
- ```csharp
1116
- // Assets/Scripts/Lobby/LobbyManager.cs
1117
- using System.Collections;
1118
- using System.Collections.Generic;
1119
- using UnityEngine;
1120
-
1121
- public class LobbyManager : MonoBehaviour
1122
- {
1123
- public static LobbyManager Instance { get; private set; }
1124
-
1125
- [SerializeField] private GameObject doorPrefab;
1126
- [SerializeField] private float doorRadius = 4f;
1127
- [SerializeField] private float doorHeight = 0f;
1128
- [SerializeField] private GameObject offlinePanel;
1129
- [SerializeField] private RoomManager roomManager;
1130
-
1131
- private List<GameObject> spawnedDoors = new List<GameObject>();
1132
- private Coroutine pollCoroutine;
1133
-
1134
- private void Awake()
1135
- {
1136
- Instance = this;
1137
- }
1138
-
1139
- private IEnumerator Start()
1140
- {
1141
- // Initial connection attempt
1142
- bool connected = false;
1143
- yield return SpacesConnection.Instance.TryConnect(result => connected = result);
1144
-
1145
- if (connected)
1146
- {
1147
- yield return SpacesConnection.Instance.FetchWorkspaces(SpawnDoors);
1148
- pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollWorkspaces());
1149
- }
1150
- else
1151
- {
1152
- ShowOffline();
1153
- }
1154
-
1155
- SpacesConnection.Instance.OnConnectionStateChanged += OnConnectionChanged;
1156
- SpacesConnection.Instance.OnWorkspacesUpdated += SpawnDoors;
1157
- }
1158
-
1159
- private void OnDestroy()
1160
- {
1161
- if (SpacesConnection.Instance != null)
1162
- {
1163
- SpacesConnection.Instance.OnConnectionStateChanged -= OnConnectionChanged;
1164
- SpacesConnection.Instance.OnWorkspacesUpdated -= SpawnDoors;
1165
- }
1166
- }
1167
-
1168
- private void OnConnectionChanged(bool connected)
1169
- {
1170
- if (connected)
1171
- {
1172
- HideOffline();
1173
- if (pollCoroutine == null)
1174
- pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollWorkspaces());
1175
- }
1176
- else
1177
- {
1178
- ShowOffline();
1179
- }
1180
- }
1181
-
1182
- private void SpawnDoors(List<WorkspaceData> workspaces)
1183
- {
1184
- if (workspaces == null) return;
1185
-
1186
- // Clear existing doors
1187
- foreach (var d in spawnedDoors) Destroy(d);
1188
- spawnedDoors.Clear();
1189
-
1190
- if (workspaces.Count == 0) return;
1191
-
1192
- float angleStep = 360f / workspaces.Count;
1193
-
1194
- for (int i = 0; i < workspaces.Count; i++)
1195
- {
1196
- float angleDeg = angleStep * i;
1197
- float angleRad = angleDeg * Mathf.Deg2Rad;
1198
-
1199
- Vector3 pos = new Vector3(
1200
- Mathf.Sin(angleRad) * doorRadius,
1201
- doorHeight,
1202
- Mathf.Cos(angleRad) * doorRadius
1203
- );
1204
-
1205
- Quaternion rot = Quaternion.LookRotation(-pos.normalized, Vector3.up);
1206
-
1207
- var doorObj = Instantiate(doorPrefab, pos, rot, transform);
1208
- var door = doorObj.GetComponent<WorkspaceDoor>();
1209
- door?.Initialize(workspaces[i]);
1210
-
1211
- spawnedDoors.Add(doorObj);
1212
- }
1213
- }
1214
-
1215
- public void EnterWorkspace(WorkspaceData workspace)
1216
- {
1217
- // Stop lobby polling
1218
- if (pollCoroutine != null)
1219
- {
1220
- StopCoroutine(pollCoroutine);
1221
- pollCoroutine = null;
1222
- }
1223
-
1224
- // Hide lobby objects
1225
- foreach (var d in spawnedDoors) d.SetActive(false);
1226
-
1227
- // Activate room
1228
- roomManager?.LoadWorkspace(workspace);
1229
- }
1230
-
1231
- public void ReturnToLobby()
1232
- {
1233
- roomManager?.UnloadWorkspace();
1234
- foreach (var d in spawnedDoors) d.SetActive(true);
1235
- pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollWorkspaces());
1236
- }
1237
-
1238
- private void ShowOffline()
1239
- {
1240
- if (offlinePanel != null) offlinePanel.SetActive(true);
1241
- }
1242
-
1243
- private void HideOffline()
1244
- {
1245
- if (offlinePanel != null) offlinePanel.SetActive(false);
1246
- }
1247
- }
1248
- ```
1249
-
1250
- - [ ] **Step 4: Commit**
1251
-
1252
- ```bash
1253
- cd C:\projects\spaces-vr
1254
- git add Assets/Scripts/Lobby/
1255
- git commit -m "feat: add lobby with workspace doors and gaze-to-enter navigation"
1256
- ```
1257
-
1258
- ---
1259
-
1260
- ### Task 7: Room Manager — Pane Spawning and Room Scaling
1261
-
1262
- **Files:**
1263
- - Create: `Assets/Scripts/Room/RoomManager.cs`
1264
-
1265
- - [ ] **Step 1: Create RoomManager**
1266
-
1267
- ```csharp
1268
- // Assets/Scripts/Room/RoomManager.cs
1269
- using System.Collections;
1270
- using System.Collections.Generic;
1271
- using UnityEngine;
1272
-
1273
- public class RoomManager : MonoBehaviour
1274
- {
1275
- [SerializeField] private GameObject panePrefab;
1276
- [SerializeField] private Light roomLight;
1277
- [SerializeField] private Transform roomContainer;
1278
-
1279
- private List<GameObject> spawnedPanes = new List<GameObject>();
1280
- private WorkspaceData currentWorkspace;
1281
- private Coroutine pollCoroutine;
1282
-
1283
- // Room scaling light presets
1284
- private static readonly Color WarmLight = new Color(1f, 0.9f, 0.8f);
1285
- private static readonly Color NeutralLight = new Color(0.9f, 0.9f, 1f);
1286
- private static readonly Color CoolLight = new Color(0.7f, 0.8f, 1f);
1287
-
1288
- public void LoadWorkspace(WorkspaceData workspace)
1289
- {
1290
- currentWorkspace = workspace;
1291
- if (roomContainer != null) roomContainer.gameObject.SetActive(true);
1292
-
1293
- StartCoroutine(FetchAndSpawnPanes());
1294
- }
1295
-
1296
- public void UnloadWorkspace()
1297
- {
1298
- if (pollCoroutine != null)
1299
- {
1300
- StopCoroutine(pollCoroutine);
1301
- pollCoroutine = null;
1302
- }
1303
-
1304
- foreach (var p in spawnedPanes) Destroy(p);
1305
- spawnedPanes.Clear();
1306
-
1307
- currentWorkspace = null;
1308
- if (roomContainer != null) roomContainer.gameObject.SetActive(false);
1309
- }
1310
-
1311
- private IEnumerator FetchAndSpawnPanes()
1312
- {
1313
- yield return SpacesConnection.Instance.FetchPanes(currentWorkspace.id, SpawnPanes);
1314
- pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollPanes(currentWorkspace.id));
1315
- }
1316
-
1317
- private void SpawnPanes(List<PaneData> panes)
1318
- {
1319
- if (panes == null) return;
1320
-
1321
- // Clear existing
1322
- foreach (var p in spawnedPanes) Destroy(p);
1323
- spawnedPanes.Clear();
1324
-
1325
- // Compute positions
1326
- var positions = PaneLayout.ComputePositions(panes.Count);
1327
-
1328
- // Parse workspace color
1329
- Color wsColor;
1330
- if (!ColorUtility.TryParseHtmlString(currentWorkspace.color, out wsColor))
1331
- wsColor = new Color(0.4f, 0.3f, 0.9f);
1332
-
1333
- // Scale room lighting based on pane count
1334
- SetRoomLighting(panes.Count);
1335
-
1336
- // Spawn panes
1337
- for (int i = 0; i < panes.Count; i++)
1338
- {
1339
- Vector3 pos = positions[i];
1340
- Quaternion rot = PaneLayout.ComputeRotation(pos);
1341
-
1342
- Transform parent = roomContainer != null ? roomContainer : transform;
1343
- var paneObj = Instantiate(panePrefab, pos, rot, parent);
1344
- var surface = paneObj.GetComponent<PaneSurface>();
1345
- surface?.Initialize(panes[i], wsColor);
1346
-
1347
- spawnedPanes.Add(paneObj);
1348
- }
1349
- }
1350
-
1351
- private void SetRoomLighting(int paneCount)
1352
- {
1353
- if (roomLight == null) return;
1354
-
1355
- if (paneCount <= 2)
1356
- {
1357
- roomLight.color = WarmLight;
1358
- roomLight.intensity = 0.6f;
1359
- }
1360
- else if (paneCount <= 5)
1361
- {
1362
- roomLight.color = NeutralLight;
1363
- roomLight.intensity = 0.8f;
1364
- }
1365
- else
1366
- {
1367
- roomLight.color = CoolLight;
1368
- roomLight.intensity = 1.0f;
1369
- }
1370
- }
1371
- }
1372
- ```
1373
-
1374
- - [ ] **Step 2: Commit**
1375
-
1376
- ```bash
1377
- cd C:\projects\spaces-vr
1378
- git add Assets/Scripts/Room/RoomManager.cs
1379
- git commit -m "feat: add room manager with pane spawning and adaptive lighting"
1380
- ```
1381
-
1382
- ---
1383
-
1384
- ### Task 8: Palm Menu (Return to Lobby)
1385
-
1386
- **Files:**
1387
- - Create: `Assets/Scripts/Interaction/PalmMenu.cs`
1388
-
1389
- - [ ] **Step 1: Create PalmMenu**
1390
-
1391
- ```csharp
1392
- // Assets/Scripts/Interaction/PalmMenu.cs
1393
- using UnityEngine;
1394
-
1395
- public class PalmMenu : MonoBehaviour
1396
- {
1397
- [SerializeField] private OVRHand hand; // left hand
1398
- [SerializeField] private OVRSkeleton skeleton;
1399
- [SerializeField] private GameObject menuPanel; // small floating UI
1400
- [SerializeField] private Renderer homeButton;
1401
- [SerializeField] private float palmUpThreshold = 0.7f; // dot product with up vector
1402
- [SerializeField] private float gazeConfirmTime = 1.0f;
1403
-
1404
- private bool menuVisible;
1405
- private float gazeTimer;
1406
- private bool isGazingHome;
1407
- private MaterialPropertyBlock propBlock;
1408
-
1409
- private void Awake()
1410
- {
1411
- propBlock = new MaterialPropertyBlock();
1412
- if (menuPanel != null) menuPanel.SetActive(false);
1413
- }
1414
-
1415
- private void Update()
1416
- {
1417
- UpdateMenuVisibility();
1418
- if (menuVisible) UpdateGazeConfirm();
1419
- }
1420
-
1421
- private void UpdateMenuVisibility()
1422
- {
1423
- if (hand == null || skeleton == null)
1424
- {
1425
- SetMenuVisible(false);
1426
- return;
1427
- }
1428
-
1429
- // Check if palm is facing up
1430
- if (skeleton.Bones == null || skeleton.Bones.Count == 0)
1431
- {
1432
- SetMenuVisible(false);
1433
- return;
1434
- }
1435
-
1436
- // Use wrist bone orientation to determine palm facing
1437
- Transform wrist = skeleton.Bones[(int)OVRSkeleton.BoneId.Hand_WristRoot]?.Transform;
1438
- if (wrist == null) { SetMenuVisible(false); return; }
1439
-
1440
- float palmDot = Vector3.Dot(wrist.up, Vector3.up);
1441
- bool palmUp = palmDot > palmUpThreshold;
1442
-
1443
- // Also check if user is looking at their palm
1444
- bool lookingAtPalm = false;
1445
- if (GazeManager.Instance?.LastHit != null)
1446
- {
1447
- float distToHand = Vector3.Distance(
1448
- GazeManager.Instance.LastHit.Value.point, wrist.position);
1449
- lookingAtPalm = distToHand < 0.2f;
1450
- }
1451
-
1452
- SetMenuVisible(palmUp && lookingAtPalm);
1453
- }
1454
-
1455
- private void SetMenuVisible(bool visible)
1456
- {
1457
- if (menuVisible == visible) return;
1458
- menuVisible = visible;
1459
- if (menuPanel != null) menuPanel.SetActive(visible);
1460
- if (!visible) gazeTimer = 0f;
1461
- }
1462
-
1463
- private void UpdateGazeConfirm()
1464
- {
1465
- // Check if gaze is on the home button
1466
- var focused = GazeManager.Instance?.FocusedObject;
1467
- if (focused != null && focused == homeButton?.gameObject)
1468
- {
1469
- gazeTimer += Time.deltaTime;
1470
-
1471
- // Update progress ring
1472
- if (homeButton != null)
1473
- {
1474
- homeButton.GetPropertyBlock(propBlock);
1475
- propBlock.SetFloat("_Progress", gazeTimer / gazeConfirmTime);
1476
- homeButton.SetPropertyBlock(propBlock);
1477
- }
1478
-
1479
- if (gazeTimer >= gazeConfirmTime)
1480
- {
1481
- gazeTimer = 0f;
1482
- ReturnToLobby();
1483
- }
1484
- }
1485
- else
1486
- {
1487
- gazeTimer = 0f;
1488
- }
1489
- }
1490
-
1491
- private void ReturnToLobby()
1492
- {
1493
- LobbyManager.Instance?.ReturnToLobby();
1494
- }
1495
- }
1496
- ```
1497
-
1498
- - [ ] **Step 2: Commit**
1499
-
1500
- ```bash
1501
- cd C:\projects\spaces-vr
1502
- git add Assets/Scripts/Interaction/PalmMenu.cs
1503
- git commit -m "feat: add palm menu with gaze-confirm return to lobby"
1504
- ```
1505
-
1506
- ---
1507
-
1508
- ### Task 9: Scene Setup and Prefab Assembly (Unity Editor)
1509
-
1510
- **Context:** This task is done entirely in the Unity Editor. It connects all the scripts to GameObjects and creates the prefabs and scenes.
1511
-
1512
- - [ ] **Step 1: Create PaneSurface prefab**
1513
-
1514
- 1. In Scene, create: 3D Object → Quad. Name it `PaneSurface`.
1515
- 2. Scale: (1.2, 0.8, 1). This is the pane surface.
1516
- 3. Create a dark material (`PaneSurface.mat`): Shader = URP/Lit, Base Color = `#0a0a0f`, Metallic = 0, Smoothness = 0.1.
1517
- 4. Add a child: 3D Object → Sphere (scale 0.02) for the status dot. Position top-left corner.
1518
- 5. Add child TextMeshPro objects for header (top of quad) and content (body).
1519
- 6. Add components to the root:
1520
- - `PaneSurface.cs`
1521
- - `GazeFocusHighlight.cs`
1522
- - `BoxCollider` (auto-added by PaneSurface RequireComponent)
1523
- 7. Wire serialized fields: `contentText`, `header`, `statusIndicator` → child objects.
1524
- 8. Drag to `Assets/Prefabs/PaneSurface.prefab`.
1525
-
1526
- - [ ] **Step 2: Create WorkspaceDoor prefab**
1527
-
1528
- 1. Create an empty GameObject named `WorkspaceDoor`.
1529
- 2. Add child: Cube scaled to (1.2, 2.2, 0.1) — the door frame. Apply `DoorFrame.mat`.
1530
- 3. Add child: TextMeshPro for name label (above frame).
1531
- 4. Add child: TextMeshPro for info label (inside frame).
1532
- 5. Add child: Point Light (range 2, intensity 0.5) for glow.
1533
- 6. Add components to root:
1534
- - `WorkspaceDoor.cs`
1535
- - `DoorInteraction.cs`
1536
- - `GazeFocusHighlight.cs`
1537
- - `BoxCollider` (size matching the door frame)
1538
- 7. Wire serialized fields.
1539
- 8. Drag to `Assets/Prefabs/WorkspaceDoor.prefab`.
1540
-
1541
- - [ ] **Step 3: Set up the main scene**
1542
-
1543
- 1. Open the default scene (or create `Assets/Scenes/Main.unity`).
1544
- 2. Ensure OVRCameraRig is present (from Task 1).
1545
- 3. Create empty GameObjects:
1546
- - `[Managers]` — add `SessionManager.cs`, `SpacesConnection.cs`, `GazeManager.cs`
1547
- - `[Lobby]` — add `LobbyManager.cs`. Wire `doorPrefab` to the WorkspaceDoor prefab.
1548
- - `[Room]` — add `RoomManager.cs`. Wire `panePrefab` to the PaneSurface prefab. Add a Directional Light child for room lighting.
1549
- - `[Interaction]` — add `HandGrabHandler.cs`. Wire `leftHand` and `rightHand` to OVRHandPrefab instances.
1550
- - `[PalmMenu]` — add `PalmMenu.cs` with a small floating Canvas child for the home button.
1551
- 4. Add OVRHandPrefab for left and right hands (from Meta XR SDK samples).
1552
- 5. Set Room container initially inactive.
1553
- 6. Create an "Offline Panel" — a TextMeshPro world-space canvas at center, initially inactive. Wire to LobbyManager's `offlinePanel` field.
1554
- 7. Wire cross-references: LobbyManager.roomManager → RoomManager, etc.
1555
-
1556
- - [ ] **Step 4: Test in editor Play Mode**
1557
-
1558
- Press Play in Unity Editor. With Meta XR Simulator:
1559
- 1. Verify lobby spawns — should see "Offline" panel if no server is running.
1560
- 2. Start Spaces server (`node bin/spaces.js --port 3457`). Hit Play again.
1561
- 3. Doors should appear for each workspace.
1562
- 4. Simulated gaze → door → should trigger confirm ring and enter room.
1563
- 5. Panes should spawn in semicircle with correct names.
1564
-
1565
- - [ ] **Step 5: Commit**
1566
-
1567
- ```bash
1568
- cd C:\projects\spaces-vr
1569
- git add -A
1570
- git commit -m "feat: assemble scenes, prefabs, and wire all components"
1571
- ```
1572
-
1573
- ---
1574
-
1575
- ### Task 10: Quest 3 Build and Deploy
1576
-
1577
- - [ ] **Step 1: Configure Android build**
1578
-
1579
- In Unity:
1580
- 1. File → Build Settings → ensure Android platform is selected
1581
- 2. Player Settings → verify: IL2CPP, ARM64, Vulkan, API 32 (from Task 1)
1582
- 3. XR Plug-in Management → verify Oculus checked
1583
- 4. Meta XR → verify Eye Tracking + Hand Tracking enabled
1584
-
1585
- - [ ] **Step 2: Build APK**
1586
-
1587
- File → Build Settings → Build → save as `Build/SpacesVR.apk`
1588
-
1589
- Expected: Build succeeds. First build takes 5-10 minutes.
1590
-
1591
- - [ ] **Step 3: Deploy to Quest 3 (when you have one)**
1592
-
1593
- Option A — Quest Link:
1594
- 1. Connect Quest via USB-C or Air Link
1595
- 2. In Unity: File → Build and Run (deploys and launches automatically)
1596
-
1597
- Option B — SideQuest:
1598
- 1. Install SideQuest on PC
1599
- 2. Connect Quest via USB
1600
- 3. Drag `Build/SpacesVR.apk` into SideQuest
1601
- 4. On Quest: Unknown Sources → Spaces VR
1602
-
1603
- - [ ] **Step 4: Commit final build config**
1604
-
1605
- ```bash
1606
- cd C:\projects\spaces-vr
1607
- git add -A
1608
- git commit -m "chore: finalize Quest 3 build configuration"
1609
- ```
1610
-
1611
- ---
1612
-
1613
- ### Summary: File Map
1614
-
1615
- ```
1616
- C:\projects\spaces-vr\Assets\Scripts\
1617
- ├── Core\
1618
- │ ├── WorkspaceData.cs # Task 2 — data models + JSON helpers
1619
- │ ├── SpacesConnection.cs # Task 2 — HTTP client + polling
1620
- │ └── SessionManager.cs # Task 2 — auth token management
1621
- ├── Interaction\
1622
- │ ├── GazeManager.cs # Task 3 — eye tracking + head-gaze fallback
1623
- │ ├── GazeFocusHighlight.cs # Task 3 — visual focus feedback
1624
- │ ├── HandGrabHandler.cs # Task 5 — grab/move/resize
1625
- │ └── PalmMenu.cs # Task 8 — palm-up home button
1626
- ├── Lobby\
1627
- │ ├── LobbyManager.cs # Task 6 — spawn doors, manage transitions
1628
- │ ├── WorkspaceDoor.cs # Task 6 — door rendering
1629
- │ └── DoorInteraction.cs # Task 6 — gaze-to-enter
1630
- ├── Room\
1631
- │ ├── RoomManager.cs # Task 7 — spawn panes, room lighting
1632
- │ ├── PaneSurface.cs # Task 4 — pane placeholder
1633
- │ └── PaneLayout.cs # Task 4 — semicircle math
1634
- └── UI\
1635
- ├── PaneHeader.cs # Task 4 — agent name/type label
1636
- └── StatusIndicator.cs # Task 4 — colored status dot
1637
-
1638
- C:\projects\spaces\src\app\api\panes\route.ts # Task 0 — workspace_id filter
1639
- ```
1
+ # Spaces VR Phase 1: Shell + Static Rooms — Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Build a native Meta Quest 3 VR app that connects to a running Spaces server and renders workspace rooms with pane placeholders, navigable via eye tracking and hand tracking.
6
+
7
+ **Architecture:** Unity project (`C:\projects\spaces-vr`) with Meta XR SDK. HTTP client polls Spaces server API. Two scenes: Lobby (doors per workspace) and WorkspaceRoom (pane placeholders in semicircle). Eye tracking for gaze focus, hand tracking for grab/move/resize.
8
+
9
+ **Tech Stack:** Unity 2022.3 LTS, C#, Meta XR SDK v68+, Meta Interaction SDK, TextMeshPro, UnityWebRequest
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-03-19-vr-phase1-shell-design.md`
12
+
13
+ **Important:** This is a Unity project. Many steps involve Unity Editor operations (creating scenes, configuring components, building prefabs) that cannot be fully automated via code. The plan provides exact C# scripts and documents which Editor steps are needed.
14
+
15
+ ---
16
+
17
+ ### Task 0: Server-Side — Add workspace_id Filter to Panes API
18
+
19
+ **Context:** The VR client needs to fetch panes for a specific workspace. The current `GET /api/panes` route only returns panes for the active workspace. We need to support `?workspace_id=N`.
20
+
21
+ **Files:**
22
+ - Modify: `C:\projects\spaces\src\app\api\panes\route.ts`
23
+
24
+ - [ ] **Step 1: Update the GET handler to accept workspace_id query param**
25
+
26
+ Replace the GET handler in `src/app/api/panes/route.ts`:
27
+
28
+ ```typescript
29
+ export async function GET(request: NextRequest) {
30
+ const user = getAuthUser(request);
31
+ return withUser(user, async () => {
32
+ await ensureInitialized();
33
+ const url = new URL(request.url);
34
+ const workspaceId = url.searchParams.get('workspace_id');
35
+ if (workspaceId) {
36
+ const { getPanesByWorkspace } = await import('@/lib/db/queries');
37
+ return NextResponse.json(getPanesByWorkspace(parseInt(workspaceId, 10)));
38
+ }
39
+ return NextResponse.json(getActivePanes());
40
+ });
41
+ }
42
+ ```
43
+
44
+ Add `getPanesByWorkspace` to the imports if not already imported:
45
+ ```typescript
46
+ import { getActivePanes, createPane, getPanesByWorkspace } from '@/lib/db/queries';
47
+ ```
48
+
49
+ - [ ] **Step 2: Verify the change**
50
+
51
+ Run: `npx tsc --noEmit 2>&1 | head -5`
52
+ Expected: No errors
53
+
54
+ - [ ] **Step 3: Commit**
55
+
56
+ ```bash
57
+ cd C:\projects\spaces
58
+ git add src/app/api/panes/route.ts
59
+ git commit -m "feat: add workspace_id filter to GET /api/panes for VR client"
60
+ ```
61
+
62
+ ---
63
+
64
+ ### Task 1: Unity Project Setup
65
+
66
+ **Context:** Create the Unity project and configure it for Quest 3 development. This task is mostly Unity Editor work.
67
+
68
+ - [ ] **Step 1: Create the Unity project**
69
+
70
+ Open Unity Hub → New Project → 3D (URP) template → Project name: `spaces-vr` → Location: `C:\projects\` → Create Project
71
+
72
+ - [ ] **Step 2: Initialize git**
73
+
74
+ ```bash
75
+ cd C:\projects\spaces-vr
76
+ git init
77
+ ```
78
+
79
+ Create `.gitignore`:
80
+
81
+ ```
82
+ # Unity
83
+ [Ll]ibrary/
84
+ [Tt]emp/
85
+ [Oo]bj/
86
+ [Bb]uild/
87
+ [Bb]uilds/
88
+ [Ll]ogs/
89
+ [Uu]ser[Ss]ettings/
90
+ *.csproj
91
+ *.unityproj
92
+ *.sln
93
+ *.suo
94
+ *.tmp
95
+ *.user
96
+ *.userprefs
97
+ *.pidb
98
+ *.booproj
99
+ *.svd
100
+ *.pdb
101
+ *.mdb
102
+ *.opendb
103
+ *.VC.db
104
+ *.pidb.meta
105
+ *.pdb.meta
106
+ *.mdb.meta
107
+ crashlytics-buildid.txt
108
+ sysinfo.txt
109
+ *.apk
110
+ *.aab
111
+ *.unitypackage
112
+ *.unitypackage.meta
113
+ ```
114
+
115
+ ```bash
116
+ git add .gitignore
117
+ git commit -m "chore: initialize spaces-vr Unity project"
118
+ ```
119
+
120
+ - [ ] **Step 3: Configure build settings for Quest 3**
121
+
122
+ In Unity Editor:
123
+ 1. File → Build Settings → Switch Platform to **Android**
124
+ 2. Player Settings:
125
+ - Other Settings → Scripting Backend: **IL2CPP**
126
+ - Other Settings → Target Architectures: check **ARM64** only
127
+ - Other Settings → Minimum API Level: **Android 12.0 (API 32)**
128
+ - Other Settings → Graphics APIs: **Vulkan** only (remove OpenGLES)
129
+ - Company Name: `Spaces`
130
+ - Product Name: `Spaces VR`
131
+ - Package Name: `com.spaces.vr`
132
+
133
+ - [ ] **Step 4: Install Meta XR SDK packages**
134
+
135
+ In Unity: Window → Package Manager → + → Add by name:
136
+ - `com.meta.xr.sdk.all` (Meta XR All-in-One SDK)
137
+
138
+ This pulls in OVR, Interaction SDK, and all Quest features.
139
+
140
+ After import, accept any "Fix All" prompts from the Meta Project Setup Tool.
141
+
142
+ - [ ] **Step 5: Configure OVR settings**
143
+
144
+ 1. In Hierarchy, delete the default Main Camera
145
+ 2. Add: right-click → XR → OVR Camera Rig
146
+ 3. On the OVRCameraRig, in OVRManager:
147
+ - Target Devices: Quest 3
148
+ - Tracking Origin Type: Floor Level
149
+ - Hand Tracking Support: Controllers and Hands
150
+ - Eye Tracking Support: Supported (check "Required" = false for fallback)
151
+ 4. Edit → Project Settings → XR Plug-in Management → check **Oculus**
152
+ 5. Edit → Project Settings → Meta XR → check: Eye Tracking, Hand Tracking
153
+
154
+ - [ ] **Step 6: Create folder structure**
155
+
156
+ In Unity Project window, create these folders:
157
+ ```
158
+ Assets/
159
+ ├── Scenes/
160
+ ├── Scripts/
161
+ │ ├── Core/
162
+ │ ├── Lobby/
163
+ │ ├── Room/
164
+ │ ├── Interaction/
165
+ │ └── UI/
166
+ ├── Prefabs/
167
+ ├── Materials/
168
+ └── Shaders/
169
+ ```
170
+
171
+ - [ ] **Step 7: Commit**
172
+
173
+ ```bash
174
+ cd C:\projects\spaces-vr
175
+ git add -A
176
+ git commit -m "feat: configure Unity project for Quest 3 with Meta XR SDK"
177
+ ```
178
+
179
+ ---
180
+
181
+ ### Task 2: Data Models and Server Connection
182
+
183
+ **Files:**
184
+ - Create: `Assets/Scripts/Core/WorkspaceData.cs`
185
+ - Create: `Assets/Scripts/Core/SpacesConnection.cs`
186
+ - Create: `Assets/Scripts/Core/SessionManager.cs`
187
+
188
+ - [ ] **Step 1: Create data models**
189
+
190
+ ```csharp
191
+ // Assets/Scripts/Core/WorkspaceData.cs
192
+ using System;
193
+ using System.Collections.Generic;
194
+
195
+ [Serializable]
196
+ public class WorkspaceData
197
+ {
198
+ public int id;
199
+ public string name;
200
+ public string color;
201
+ public int paneCount;
202
+ }
203
+
204
+ [Serializable]
205
+ public class PaneData
206
+ {
207
+ public string id;
208
+ public string agentType;
209
+ public string title;
210
+ }
211
+
212
+ [Serializable]
213
+ public class WorkspaceListResponse
214
+ {
215
+ public List<WorkspaceData> items;
216
+ }
217
+
218
+ // Helper to deserialize JSON array (Unity's JsonUtility doesn't handle top-level arrays)
219
+ public static class JsonArrayHelper
220
+ {
221
+ public static List<T> FromJson<T>(string json)
222
+ {
223
+ string wrapped = "{\"items\":" + json + "}";
224
+ var wrapper = UnityEngine.JsonUtility.FromJson<Wrapper<T>>(wrapped);
225
+ return wrapper.items;
226
+ }
227
+
228
+ [Serializable]
229
+ private class Wrapper<T>
230
+ {
231
+ public List<T> items;
232
+ }
233
+ }
234
+ ```
235
+
236
+ - [ ] **Step 2: Create session manager**
237
+
238
+ ```csharp
239
+ // Assets/Scripts/Core/SessionManager.cs
240
+ using UnityEngine;
241
+
242
+ public class SessionManager : MonoBehaviour
243
+ {
244
+ public static SessionManager Instance { get; private set; }
245
+
246
+ public string ServerUrl { get; set; } = "http://localhost:3457";
247
+ public string SessionCookie { get; private set; }
248
+ public bool IsAuthenticated => !string.IsNullOrEmpty(SessionCookie);
249
+
250
+ private void Awake()
251
+ {
252
+ if (Instance != null) { Destroy(gameObject); return; }
253
+ Instance = this;
254
+ DontDestroyOnLoad(gameObject);
255
+ }
256
+
257
+ public void SetCookie(string cookie)
258
+ {
259
+ SessionCookie = cookie;
260
+ }
261
+
262
+ public void ClearSession()
263
+ {
264
+ SessionCookie = null;
265
+ }
266
+ }
267
+ ```
268
+
269
+ - [ ] **Step 3: Create HTTP connection client**
270
+
271
+ ```csharp
272
+ // Assets/Scripts/Core/SpacesConnection.cs
273
+ using System;
274
+ using System.Collections;
275
+ using System.Collections.Generic;
276
+ using UnityEngine;
277
+ using UnityEngine.Networking;
278
+
279
+ public class SpacesConnection : MonoBehaviour
280
+ {
281
+ public static SpacesConnection Instance { get; private set; }
282
+
283
+ [SerializeField] private float workspacePollingInterval = 30f;
284
+ [SerializeField] private float panePollingInterval = 10f;
285
+ [SerializeField] private float requestTimeout = 5f;
286
+ [SerializeField] private int maxConsecutiveFailures = 3;
287
+
288
+ public event Action<List<WorkspaceData>> OnWorkspacesUpdated;
289
+ public event Action<List<PaneData>> OnPanesUpdated;
290
+ public event Action<bool> OnConnectionStateChanged;
291
+
292
+ private int consecutiveFailures;
293
+ private bool isConnected;
294
+
295
+ private void Awake()
296
+ {
297
+ if (Instance != null) { Destroy(gameObject); return; }
298
+ Instance = this;
299
+ DontDestroyOnLoad(gameObject);
300
+ }
301
+
302
+ private string BaseUrl => SessionManager.Instance.ServerUrl;
303
+
304
+ public IEnumerator FetchWorkspaces(Action<List<WorkspaceData>> callback)
305
+ {
306
+ using var req = UnityWebRequest.Get($"{BaseUrl}/api/workspaces");
307
+ req.timeout = (int)requestTimeout;
308
+ if (SessionManager.Instance.IsAuthenticated)
309
+ req.SetRequestHeader("Cookie", SessionManager.Instance.SessionCookie);
310
+
311
+ yield return req.SendWebRequest();
312
+
313
+ if (req.result == UnityWebRequest.Result.Success)
314
+ {
315
+ consecutiveFailures = 0;
316
+ if (!isConnected) { isConnected = true; OnConnectionStateChanged?.Invoke(true); }
317
+
318
+ var workspaces = JsonArrayHelper.FromJson<WorkspaceData>(req.downloadHandler.text);
319
+ callback?.Invoke(workspaces);
320
+ OnWorkspacesUpdated?.Invoke(workspaces);
321
+ }
322
+ else
323
+ {
324
+ HandleFailure();
325
+ callback?.Invoke(null);
326
+ }
327
+ }
328
+
329
+ public IEnumerator FetchPanes(int workspaceId, Action<List<PaneData>> callback)
330
+ {
331
+ using var req = UnityWebRequest.Get($"{BaseUrl}/api/panes?workspace_id={workspaceId}");
332
+ req.timeout = (int)requestTimeout;
333
+ if (SessionManager.Instance.IsAuthenticated)
334
+ req.SetRequestHeader("Cookie", SessionManager.Instance.SessionCookie);
335
+
336
+ yield return req.SendWebRequest();
337
+
338
+ if (req.result == UnityWebRequest.Result.Success)
339
+ {
340
+ consecutiveFailures = 0;
341
+ var panes = JsonArrayHelper.FromJson<PaneData>(req.downloadHandler.text);
342
+ callback?.Invoke(panes);
343
+ OnPanesUpdated?.Invoke(panes);
344
+ }
345
+ else
346
+ {
347
+ HandleFailure();
348
+ callback?.Invoke(null);
349
+ }
350
+ }
351
+
352
+ public IEnumerator TryConnect(Action<bool> callback)
353
+ {
354
+ // Try unauthenticated first
355
+ using var req = UnityWebRequest.Get($"{BaseUrl}/api/workspaces");
356
+ req.timeout = (int)requestTimeout;
357
+ yield return req.SendWebRequest();
358
+
359
+ if (req.result == UnityWebRequest.Result.Success)
360
+ {
361
+ isConnected = true;
362
+ OnConnectionStateChanged?.Invoke(true);
363
+ callback?.Invoke(true);
364
+ }
365
+ else if (req.responseCode == 401)
366
+ {
367
+ // Auth required — need login UI (Phase 1: show message)
368
+ Debug.Log("[SpacesVR] Server requires authentication");
369
+ callback?.Invoke(false);
370
+ }
371
+ else
372
+ {
373
+ HandleFailure();
374
+ callback?.Invoke(false);
375
+ }
376
+ }
377
+
378
+ private void HandleFailure()
379
+ {
380
+ consecutiveFailures++;
381
+ if (consecutiveFailures >= maxConsecutiveFailures && isConnected)
382
+ {
383
+ isConnected = false;
384
+ OnConnectionStateChanged?.Invoke(false);
385
+ }
386
+ }
387
+
388
+ // Coroutine-based polling helpers
389
+ public IEnumerator PollWorkspaces()
390
+ {
391
+ while (true)
392
+ {
393
+ yield return FetchWorkspaces(null);
394
+ yield return new WaitForSeconds(workspacePollingInterval);
395
+ }
396
+ }
397
+
398
+ public IEnumerator PollPanes(int workspaceId)
399
+ {
400
+ while (true)
401
+ {
402
+ yield return FetchPanes(workspaceId, null);
403
+ yield return new WaitForSeconds(panePollingInterval);
404
+ }
405
+ }
406
+ }
407
+ ```
408
+
409
+ - [ ] **Step 4: Commit**
410
+
411
+ ```bash
412
+ cd C:\projects\spaces-vr
413
+ git add Assets/Scripts/Core/
414
+ git commit -m "feat: add data models and HTTP connection client"
415
+ ```
416
+
417
+ ---
418
+
419
+ ### Task 3: Gaze Manager (Eye Tracking + Fallback)
420
+
421
+ **Files:**
422
+ - Create: `Assets/Scripts/Interaction/GazeManager.cs`
423
+ - Create: `Assets/Scripts/Interaction/GazeFocusHighlight.cs`
424
+
425
+ - [ ] **Step 1: Create GazeManager**
426
+
427
+ ```csharp
428
+ // Assets/Scripts/Interaction/GazeManager.cs
429
+ using UnityEngine;
430
+
431
+ public class GazeManager : MonoBehaviour
432
+ {
433
+ public static GazeManager Instance { get; private set; }
434
+
435
+ [SerializeField] private float focusDelay = 0.3f; // 300ms sustained gaze
436
+ [SerializeField] private float unfocusDelay = 0.2f;
437
+ [SerializeField] private float maxRayDistance = 20f;
438
+ [SerializeField] private LayerMask gazeLayerMask = ~0;
439
+
440
+ public GameObject FocusedObject { get; private set; }
441
+ public RaycastHit? LastHit { get; private set; }
442
+ public event System.Action<GameObject> OnFocusChanged;
443
+
444
+ private OVREyeGaze leftEyeGaze;
445
+ private OVREyeGaze rightEyeGaze;
446
+ private bool useEyeTracking;
447
+ private Transform centerEye;
448
+
449
+ private GameObject candidateObject;
450
+ private float candidateTime;
451
+ private float unfocusTime;
452
+
453
+ private void Awake()
454
+ {
455
+ if (Instance != null) { Destroy(gameObject); return; }
456
+ Instance = this;
457
+ }
458
+
459
+ private void Start()
460
+ {
461
+ // Try to find eye tracking components
462
+ var eyeGazes = FindObjectsOfType<OVREyeGaze>();
463
+ foreach (var eg in eyeGazes)
464
+ {
465
+ if (eg.Eye == OVREyeGaze.EyeId.Combined || eg.Eye == OVREyeGaze.EyeId.Left)
466
+ leftEyeGaze = eg;
467
+ }
468
+
469
+ // Find center eye anchor for fallback
470
+ var rig = FindObjectOfType<OVRCameraRig>();
471
+ if (rig != null) centerEye = rig.centerEyeAnchor;
472
+
473
+ // Check if eye tracking is available
474
+ useEyeTracking = OVRPlugin.eyeTrackingEnabled;
475
+ if (!useEyeTracking)
476
+ Debug.Log("[GazeManager] Eye tracking unavailable, using head-gaze fallback");
477
+ }
478
+
479
+ private void Update()
480
+ {
481
+ Ray gazeRay = GetGazeRay();
482
+ RaycastHit hit;
483
+ bool didHit = Physics.Raycast(gazeRay, out hit, maxRayDistance, gazeLayerMask);
484
+
485
+ if (didHit)
486
+ {
487
+ LastHit = hit;
488
+ var hitObj = hit.collider.gameObject;
489
+
490
+ if (hitObj == FocusedObject)
491
+ {
492
+ // Still looking at focused object — reset unfocus timer
493
+ unfocusTime = 0f;
494
+ }
495
+ else if (hitObj == candidateObject)
496
+ {
497
+ // Still looking at candidate — accumulate focus time
498
+ candidateTime += Time.deltaTime;
499
+ if (candidateTime >= focusDelay)
500
+ {
501
+ SetFocus(hitObj);
502
+ }
503
+ }
504
+ else
505
+ {
506
+ // New candidate
507
+ candidateObject = hitObj;
508
+ candidateTime = 0f;
509
+ }
510
+ }
511
+ else
512
+ {
513
+ LastHit = null;
514
+ candidateObject = null;
515
+ candidateTime = 0f;
516
+
517
+ if (FocusedObject != null)
518
+ {
519
+ unfocusTime += Time.deltaTime;
520
+ if (unfocusTime >= unfocusDelay)
521
+ {
522
+ SetFocus(null);
523
+ }
524
+ }
525
+ }
526
+ }
527
+
528
+ private Ray GetGazeRay()
529
+ {
530
+ if (useEyeTracking && leftEyeGaze != null)
531
+ {
532
+ return new Ray(leftEyeGaze.transform.position, leftEyeGaze.transform.forward);
533
+ }
534
+
535
+ // Head-gaze fallback
536
+ if (centerEye != null)
537
+ {
538
+ return new Ray(centerEye.position, centerEye.forward);
539
+ }
540
+
541
+ return new Ray(Camera.main.transform.position, Camera.main.transform.forward);
542
+ }
543
+
544
+ private void SetFocus(GameObject obj)
545
+ {
546
+ if (FocusedObject == obj) return;
547
+
548
+ // Notify old object
549
+ if (FocusedObject != null)
550
+ {
551
+ var highlight = FocusedObject.GetComponent<GazeFocusHighlight>();
552
+ if (highlight != null) highlight.OnUnfocus();
553
+ }
554
+
555
+ FocusedObject = obj;
556
+ unfocusTime = 0f;
557
+ candidateObject = null;
558
+ candidateTime = 0f;
559
+
560
+ // Notify new object
561
+ if (FocusedObject != null)
562
+ {
563
+ var highlight = FocusedObject.GetComponent<GazeFocusHighlight>();
564
+ if (highlight != null) highlight.OnFocus();
565
+ }
566
+
567
+ OnFocusChanged?.Invoke(FocusedObject);
568
+ }
569
+ }
570
+ ```
571
+
572
+ - [ ] **Step 2: Create GazeFocusHighlight**
573
+
574
+ ```csharp
575
+ // Assets/Scripts/Interaction/GazeFocusHighlight.cs
576
+ using UnityEngine;
577
+
578
+ public class GazeFocusHighlight : MonoBehaviour
579
+ {
580
+ [SerializeField] private float highlightScale = 1.02f;
581
+ [SerializeField] private float transitionSpeed = 8f;
582
+ [SerializeField] private Color highlightColor = new Color(0.5f, 0.3f, 1f, 0.3f); // purple glow
583
+
584
+ private Vector3 originalScale;
585
+ private bool isFocused;
586
+ private float focusLerp; // 0 = unfocused, 1 = focused
587
+ private Renderer[] renderers;
588
+ private MaterialPropertyBlock propBlock;
589
+
590
+ private void Awake()
591
+ {
592
+ originalScale = transform.localScale;
593
+ renderers = GetComponentsInChildren<Renderer>();
594
+ propBlock = new MaterialPropertyBlock();
595
+ }
596
+
597
+ private void Update()
598
+ {
599
+ float target = isFocused ? 1f : 0f;
600
+ focusLerp = Mathf.MoveTowards(focusLerp, target, Time.deltaTime * transitionSpeed);
601
+
602
+ // Scale
603
+ transform.localScale = Vector3.Lerp(originalScale, originalScale * highlightScale, focusLerp);
604
+
605
+ // Emission glow
606
+ foreach (var r in renderers)
607
+ {
608
+ r.GetPropertyBlock(propBlock);
609
+ propBlock.SetColor("_EmissionColor", highlightColor * focusLerp);
610
+ r.SetPropertyBlock(propBlock);
611
+ }
612
+ }
613
+
614
+ public void OnFocus()
615
+ {
616
+ isFocused = true;
617
+ }
618
+
619
+ public void OnUnfocus()
620
+ {
621
+ isFocused = false;
622
+ }
623
+ }
624
+ ```
625
+
626
+ - [ ] **Step 3: Commit**
627
+
628
+ ```bash
629
+ cd C:\projects\spaces-vr
630
+ git add Assets/Scripts/Interaction/GazeManager.cs Assets/Scripts/Interaction/GazeFocusHighlight.cs
631
+ git commit -m "feat: add gaze manager with eye tracking and head-gaze fallback"
632
+ ```
633
+
634
+ ---
635
+
636
+ ### Task 4: Pane Layout Math and Pane Surface
637
+
638
+ **Files:**
639
+ - Create: `Assets/Scripts/Room/PaneLayout.cs`
640
+ - Create: `Assets/Scripts/Room/PaneSurface.cs`
641
+ - Create: `Assets/Scripts/UI/PaneHeader.cs`
642
+ - Create: `Assets/Scripts/UI/StatusIndicator.cs`
643
+
644
+ - [ ] **Step 1: Create PaneLayout (semicircle positioning)**
645
+
646
+ ```csharp
647
+ // Assets/Scripts/Room/PaneLayout.cs
648
+ using UnityEngine;
649
+
650
+ public static class PaneLayout
651
+ {
652
+ public static Vector3[] ComputePositions(int paneCount, float radius = 2f, float eyeHeight = 1.6f)
653
+ {
654
+ if (paneCount <= 0) return new Vector3[0];
655
+
656
+ var positions = new Vector3[paneCount];
657
+
658
+ if (paneCount == 1)
659
+ {
660
+ positions[0] = new Vector3(0, eyeHeight, radius);
661
+ return positions;
662
+ }
663
+
664
+ // Arc grows with pane count
665
+ float totalArcDeg;
666
+ if (paneCount <= 2) totalArcDeg = 40f;
667
+ else if (paneCount <= 4) totalArcDeg = 80f;
668
+ else if (paneCount <= 6) totalArcDeg = 120f;
669
+ else totalArcDeg = 160f;
670
+
671
+ float startAngle = 90f - totalArcDeg / 2f; // center the arc at 90° (forward)
672
+ float step = totalArcDeg / (paneCount - 1);
673
+
674
+ for (int i = 0; i < paneCount; i++)
675
+ {
676
+ float angleDeg = startAngle + step * i;
677
+ float angleRad = angleDeg * Mathf.Deg2Rad;
678
+ float x = Mathf.Cos(angleRad) * radius;
679
+ float z = Mathf.Sin(angleRad) * radius;
680
+ positions[i] = new Vector3(x, eyeHeight, z);
681
+ }
682
+
683
+ return positions;
684
+ }
685
+
686
+ public static Quaternion ComputeRotation(Vector3 panePosition)
687
+ {
688
+ // Face inward toward room center (Y-axis billboard only)
689
+ Vector3 toCenter = -new Vector3(panePosition.x, 0, panePosition.z).normalized;
690
+ return Quaternion.LookRotation(toCenter, Vector3.up);
691
+ }
692
+ }
693
+ ```
694
+
695
+ - [ ] **Step 2: Create StatusIndicator**
696
+
697
+ ```csharp
698
+ // Assets/Scripts/UI/StatusIndicator.cs
699
+ using UnityEngine;
700
+
701
+ public enum PaneStatus
702
+ {
703
+ Idle, // gray
704
+ Active, // green
705
+ Waiting, // amber
706
+ Error // red
707
+ }
708
+
709
+ public class StatusIndicator : MonoBehaviour
710
+ {
711
+ [SerializeField] private Renderer dotRenderer;
712
+
713
+ private static readonly Color IdleColor = new Color(0.4f, 0.4f, 0.4f);
714
+ private static readonly Color ActiveColor = new Color(0.2f, 0.8f, 0.2f);
715
+ private static readonly Color WaitingColor = new Color(0.9f, 0.7f, 0.1f);
716
+ private static readonly Color ErrorColor = new Color(0.9f, 0.2f, 0.2f);
717
+
718
+ private MaterialPropertyBlock propBlock;
719
+
720
+ private void Awake()
721
+ {
722
+ propBlock = new MaterialPropertyBlock();
723
+ }
724
+
725
+ public void SetStatus(PaneStatus status)
726
+ {
727
+ Color c = status switch
728
+ {
729
+ PaneStatus.Active => ActiveColor,
730
+ PaneStatus.Waiting => WaitingColor,
731
+ PaneStatus.Error => ErrorColor,
732
+ _ => IdleColor,
733
+ };
734
+
735
+ if (dotRenderer != null)
736
+ {
737
+ dotRenderer.GetPropertyBlock(propBlock);
738
+ propBlock.SetColor("_Color", c);
739
+ propBlock.SetColor("_EmissionColor", c * 0.5f);
740
+ dotRenderer.SetPropertyBlock(propBlock);
741
+ }
742
+ }
743
+ }
744
+ ```
745
+
746
+ - [ ] **Step 3: Create PaneHeader**
747
+
748
+ ```csharp
749
+ // Assets/Scripts/UI/PaneHeader.cs
750
+ using TMPro;
751
+ using UnityEngine;
752
+
753
+ public class PaneHeader : MonoBehaviour
754
+ {
755
+ [SerializeField] private TextMeshPro titleText;
756
+ [SerializeField] private TextMeshPro subtitleText;
757
+ [SerializeField] private Renderer accentBar;
758
+
759
+ private MaterialPropertyBlock propBlock;
760
+
761
+ private void Awake()
762
+ {
763
+ propBlock = new MaterialPropertyBlock();
764
+ }
765
+
766
+ public void SetInfo(string agentType, string title, Color workspaceColor)
767
+ {
768
+ if (titleText != null)
769
+ titleText.text = string.IsNullOrEmpty(title) ? agentType : title;
770
+
771
+ if (subtitleText != null)
772
+ subtitleText.text = agentType;
773
+
774
+ if (accentBar != null)
775
+ {
776
+ accentBar.GetPropertyBlock(propBlock);
777
+ propBlock.SetColor("_Color", workspaceColor);
778
+ accentBar.SetPropertyBlock(propBlock);
779
+ }
780
+ }
781
+ }
782
+ ```
783
+
784
+ - [ ] **Step 4: Create PaneSurface**
785
+
786
+ ```csharp
787
+ // Assets/Scripts/Room/PaneSurface.cs
788
+ using TMPro;
789
+ using UnityEngine;
790
+
791
+ [RequireComponent(typeof(BoxCollider))]
792
+ [RequireComponent(typeof(GazeFocusHighlight))]
793
+ public class PaneSurface : MonoBehaviour
794
+ {
795
+ [SerializeField] private TextMeshPro contentText;
796
+ [SerializeField] private PaneHeader header;
797
+ [SerializeField] private StatusIndicator statusIndicator;
798
+
799
+ public string PaneId { get; private set; }
800
+ public PaneData Data { get; private set; }
801
+
802
+ private static readonly Vector3 DefaultSize = new Vector3(1.2f, 0.8f, 0.01f);
803
+ private static readonly Vector3 MinSize = new Vector3(0.6f, 0.4f, 0.01f);
804
+ private static readonly Vector3 MaxSize = new Vector3(2.4f, 1.6f, 0.01f);
805
+
806
+ public void Initialize(PaneData data, Color workspaceColor)
807
+ {
808
+ Data = data;
809
+ PaneId = data.id;
810
+
811
+ header?.SetInfo(data.agentType, data.title, workspaceColor);
812
+ statusIndicator?.SetStatus(PaneStatus.Idle); // Phase 1: always idle
813
+
814
+ if (contentText != null)
815
+ contentText.text = $"<color=#666>{data.agentType}\nConnecting...</color>";
816
+ }
817
+
818
+ public void SetSize(Vector3 newSize)
819
+ {
820
+ newSize.x = Mathf.Clamp(newSize.x, MinSize.x, MaxSize.x);
821
+ newSize.y = Mathf.Clamp(newSize.y, MinSize.y, MaxSize.y);
822
+ newSize.z = DefaultSize.z;
823
+ transform.localScale = newSize;
824
+ }
825
+ }
826
+ ```
827
+
828
+ - [ ] **Step 5: Commit**
829
+
830
+ ```bash
831
+ cd C:\projects\spaces-vr
832
+ git add Assets/Scripts/Room/ Assets/Scripts/UI/
833
+ git commit -m "feat: add pane layout, surface, header, and status indicator"
834
+ ```
835
+
836
+ ---
837
+
838
+ ### Task 5: Hand Tracking — Grab and Resize
839
+
840
+ **Files:**
841
+ - Create: `Assets/Scripts/Interaction/HandGrabHandler.cs`
842
+
843
+ - [ ] **Step 1: Create HandGrabHandler**
844
+
845
+ ```csharp
846
+ // Assets/Scripts/Interaction/HandGrabHandler.cs
847
+ using UnityEngine;
848
+
849
+ public class HandGrabHandler : MonoBehaviour
850
+ {
851
+ [SerializeField] private OVRHand leftHand;
852
+ [SerializeField] private OVRHand rightHand;
853
+ [SerializeField] private float grabDistance = 0.15f;
854
+ [SerializeField] private float smoothing = 15f;
855
+ [SerializeField] private float minDistFromCenter = 1f;
856
+ [SerializeField] private float maxDistFromCenter = 4f;
857
+ [SerializeField] private float minHeight = 0.5f;
858
+ [SerializeField] private float maxHeight = 3f;
859
+
860
+ private PaneSurface grabbedPane;
861
+ private Transform grabbingHand;
862
+ private Vector3 grabOffset;
863
+
864
+ // Two-hand resize state
865
+ private bool isResizing;
866
+ private float initialPinchDistance;
867
+ private Vector3 initialPaneScale;
868
+
869
+ private void Update()
870
+ {
871
+ bool leftPinch = leftHand != null && leftHand.GetFingerIsPinching(OVRHand.HandFinger.Index);
872
+ bool rightPinch = rightHand != null && rightHand.GetFingerIsPinching(OVRHand.HandFinger.Index);
873
+
874
+ // Two-hand resize
875
+ if (leftPinch && rightPinch && grabbedPane != null)
876
+ {
877
+ HandleResize();
878
+ return;
879
+ }
880
+
881
+ if (isResizing)
882
+ {
883
+ isResizing = false;
884
+ }
885
+
886
+ // Single-hand grab
887
+ if (!leftPinch && !rightPinch)
888
+ {
889
+ grabbedPane = null;
890
+ grabbingHand = null;
891
+ return;
892
+ }
893
+
894
+ Transform activeHand = leftPinch ? leftHand.transform : rightHand.transform;
895
+
896
+ if (grabbedPane == null)
897
+ {
898
+ // Try to grab
899
+ TryGrab(activeHand);
900
+ }
901
+ else if (grabbingHand == activeHand)
902
+ {
903
+ // Move grabbed pane
904
+ MoveGrabbedPane();
905
+ }
906
+ }
907
+
908
+ private void TryGrab(Transform hand)
909
+ {
910
+ Collider[] hits = Physics.OverlapSphere(hand.position, grabDistance);
911
+ foreach (var hit in hits)
912
+ {
913
+ var pane = hit.GetComponent<PaneSurface>();
914
+ if (pane != null)
915
+ {
916
+ grabbedPane = pane;
917
+ grabbingHand = hand;
918
+ grabOffset = pane.transform.position - hand.position;
919
+ return;
920
+ }
921
+ }
922
+ }
923
+
924
+ private void MoveGrabbedPane()
925
+ {
926
+ Vector3 targetPos = grabbingHand.position + grabOffset;
927
+
928
+ // Constrain position
929
+ float dist = new Vector2(targetPos.x, targetPos.z).magnitude;
930
+ if (dist < minDistFromCenter || dist > maxDistFromCenter)
931
+ {
932
+ Vector2 dir = new Vector2(targetPos.x, targetPos.z).normalized;
933
+ dist = Mathf.Clamp(dist, minDistFromCenter, maxDistFromCenter);
934
+ targetPos.x = dir.x * dist;
935
+ targetPos.z = dir.y * dist;
936
+ }
937
+ targetPos.y = Mathf.Clamp(targetPos.y, minHeight, maxHeight);
938
+
939
+ grabbedPane.transform.position = Vector3.Lerp(
940
+ grabbedPane.transform.position, targetPos, Time.deltaTime * smoothing);
941
+
942
+ // Re-orient to face center
943
+ grabbedPane.transform.rotation = PaneLayout.ComputeRotation(grabbedPane.transform.position);
944
+ }
945
+
946
+ private void HandleResize()
947
+ {
948
+ float currentDist = Vector3.Distance(leftHand.transform.position, rightHand.transform.position);
949
+
950
+ if (!isResizing)
951
+ {
952
+ isResizing = true;
953
+ initialPinchDistance = currentDist;
954
+ initialPaneScale = grabbedPane.transform.localScale;
955
+ return;
956
+ }
957
+
958
+ float scaleFactor = currentDist / initialPinchDistance;
959
+ Vector3 newScale = initialPaneScale * scaleFactor;
960
+ grabbedPane.SetSize(newScale);
961
+ }
962
+ }
963
+ ```
964
+
965
+ - [ ] **Step 2: Commit**
966
+
967
+ ```bash
968
+ cd C:\projects\spaces-vr
969
+ git add Assets/Scripts/Interaction/HandGrabHandler.cs
970
+ git commit -m "feat: add hand tracking grab-to-move and pinch-to-resize"
971
+ ```
972
+
973
+ ---
974
+
975
+ ### Task 6: Lobby Scene — Door Spawning and Navigation
976
+
977
+ **Files:**
978
+ - Create: `Assets/Scripts/Lobby/LobbyManager.cs`
979
+ - Create: `Assets/Scripts/Lobby/WorkspaceDoor.cs`
980
+ - Create: `Assets/Scripts/Lobby/DoorInteraction.cs`
981
+
982
+ - [ ] **Step 1: Create WorkspaceDoor**
983
+
984
+ ```csharp
985
+ // Assets/Scripts/Lobby/WorkspaceDoor.cs
986
+ using TMPro;
987
+ using UnityEngine;
988
+
989
+ public class WorkspaceDoor : MonoBehaviour
990
+ {
991
+ [SerializeField] private TextMeshPro nameLabel;
992
+ [SerializeField] private TextMeshPro infoLabel;
993
+ [SerializeField] private Renderer frameRenderer;
994
+ [SerializeField] private Renderer glowRenderer;
995
+
996
+ public WorkspaceData Data { get; private set; }
997
+
998
+ private MaterialPropertyBlock propBlock;
999
+
1000
+ private void Awake()
1001
+ {
1002
+ propBlock = new MaterialPropertyBlock();
1003
+ }
1004
+
1005
+ public void Initialize(WorkspaceData data)
1006
+ {
1007
+ Data = data;
1008
+
1009
+ if (nameLabel != null) nameLabel.text = data.name;
1010
+ if (infoLabel != null) infoLabel.text = $"{data.paneCount} pane{(data.paneCount != 1 ? "s" : "")}";
1011
+
1012
+ Color wsColor;
1013
+ if (!ColorUtility.TryParseHtmlString(data.color, out wsColor))
1014
+ wsColor = new Color(0.4f, 0.3f, 0.9f); // default purple
1015
+
1016
+ if (frameRenderer != null)
1017
+ {
1018
+ frameRenderer.GetPropertyBlock(propBlock);
1019
+ propBlock.SetColor("_Color", wsColor);
1020
+ frameRenderer.SetPropertyBlock(propBlock);
1021
+ }
1022
+
1023
+ if (glowRenderer != null)
1024
+ {
1025
+ glowRenderer.GetPropertyBlock(propBlock);
1026
+ propBlock.SetColor("_EmissionColor", wsColor * 0.3f);
1027
+ glowRenderer.SetPropertyBlock(propBlock);
1028
+ }
1029
+ }
1030
+ }
1031
+ ```
1032
+
1033
+ - [ ] **Step 2: Create DoorInteraction (gaze-to-enter)**
1034
+
1035
+ ```csharp
1036
+ // Assets/Scripts/Lobby/DoorInteraction.cs
1037
+ using UnityEngine;
1038
+
1039
+ [RequireComponent(typeof(GazeFocusHighlight))]
1040
+ public class DoorInteraction : MonoBehaviour
1041
+ {
1042
+ [SerializeField] private float confirmTime = 1.5f;
1043
+ [SerializeField] private Renderer confirmRing; // a progress ring UI element
1044
+
1045
+ private float gazeTimer;
1046
+ private bool isGazed;
1047
+ private WorkspaceDoor door;
1048
+ private MaterialPropertyBlock propBlock;
1049
+
1050
+ private void Awake()
1051
+ {
1052
+ door = GetComponent<WorkspaceDoor>();
1053
+ propBlock = new MaterialPropertyBlock();
1054
+ }
1055
+
1056
+ private void OnEnable()
1057
+ {
1058
+ if (GazeManager.Instance != null)
1059
+ GazeManager.Instance.OnFocusChanged += HandleFocusChanged;
1060
+ }
1061
+
1062
+ private void OnDisable()
1063
+ {
1064
+ if (GazeManager.Instance != null)
1065
+ GazeManager.Instance.OnFocusChanged -= HandleFocusChanged;
1066
+ }
1067
+
1068
+ private void HandleFocusChanged(GameObject focused)
1069
+ {
1070
+ isGazed = (focused == gameObject);
1071
+ if (!isGazed)
1072
+ {
1073
+ gazeTimer = 0f;
1074
+ UpdateRing(0f);
1075
+ }
1076
+ }
1077
+
1078
+ private void Update()
1079
+ {
1080
+ if (!isGazed) return;
1081
+
1082
+ gazeTimer += Time.deltaTime;
1083
+ UpdateRing(gazeTimer / confirmTime);
1084
+
1085
+ if (gazeTimer >= confirmTime)
1086
+ {
1087
+ gazeTimer = 0f;
1088
+ EnterWorkspace();
1089
+ }
1090
+ }
1091
+
1092
+ private void UpdateRing(float progress)
1093
+ {
1094
+ if (confirmRing != null)
1095
+ {
1096
+ confirmRing.gameObject.SetActive(progress > 0f);
1097
+ confirmRing.GetPropertyBlock(propBlock);
1098
+ propBlock.SetFloat("_Progress", Mathf.Clamp01(progress));
1099
+ confirmRing.SetPropertyBlock(propBlock);
1100
+ }
1101
+ }
1102
+
1103
+ private void EnterWorkspace()
1104
+ {
1105
+ if (door?.Data != null)
1106
+ {
1107
+ LobbyManager.Instance?.EnterWorkspace(door.Data);
1108
+ }
1109
+ }
1110
+ }
1111
+ ```
1112
+
1113
+ - [ ] **Step 3: Create LobbyManager**
1114
+
1115
+ ```csharp
1116
+ // Assets/Scripts/Lobby/LobbyManager.cs
1117
+ using System.Collections;
1118
+ using System.Collections.Generic;
1119
+ using UnityEngine;
1120
+
1121
+ public class LobbyManager : MonoBehaviour
1122
+ {
1123
+ public static LobbyManager Instance { get; private set; }
1124
+
1125
+ [SerializeField] private GameObject doorPrefab;
1126
+ [SerializeField] private float doorRadius = 4f;
1127
+ [SerializeField] private float doorHeight = 0f;
1128
+ [SerializeField] private GameObject offlinePanel;
1129
+ [SerializeField] private RoomManager roomManager;
1130
+
1131
+ private List<GameObject> spawnedDoors = new List<GameObject>();
1132
+ private Coroutine pollCoroutine;
1133
+
1134
+ private void Awake()
1135
+ {
1136
+ Instance = this;
1137
+ }
1138
+
1139
+ private IEnumerator Start()
1140
+ {
1141
+ // Initial connection attempt
1142
+ bool connected = false;
1143
+ yield return SpacesConnection.Instance.TryConnect(result => connected = result);
1144
+
1145
+ if (connected)
1146
+ {
1147
+ yield return SpacesConnection.Instance.FetchWorkspaces(SpawnDoors);
1148
+ pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollWorkspaces());
1149
+ }
1150
+ else
1151
+ {
1152
+ ShowOffline();
1153
+ }
1154
+
1155
+ SpacesConnection.Instance.OnConnectionStateChanged += OnConnectionChanged;
1156
+ SpacesConnection.Instance.OnWorkspacesUpdated += SpawnDoors;
1157
+ }
1158
+
1159
+ private void OnDestroy()
1160
+ {
1161
+ if (SpacesConnection.Instance != null)
1162
+ {
1163
+ SpacesConnection.Instance.OnConnectionStateChanged -= OnConnectionChanged;
1164
+ SpacesConnection.Instance.OnWorkspacesUpdated -= SpawnDoors;
1165
+ }
1166
+ }
1167
+
1168
+ private void OnConnectionChanged(bool connected)
1169
+ {
1170
+ if (connected)
1171
+ {
1172
+ HideOffline();
1173
+ if (pollCoroutine == null)
1174
+ pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollWorkspaces());
1175
+ }
1176
+ else
1177
+ {
1178
+ ShowOffline();
1179
+ }
1180
+ }
1181
+
1182
+ private void SpawnDoors(List<WorkspaceData> workspaces)
1183
+ {
1184
+ if (workspaces == null) return;
1185
+
1186
+ // Clear existing doors
1187
+ foreach (var d in spawnedDoors) Destroy(d);
1188
+ spawnedDoors.Clear();
1189
+
1190
+ if (workspaces.Count == 0) return;
1191
+
1192
+ float angleStep = 360f / workspaces.Count;
1193
+
1194
+ for (int i = 0; i < workspaces.Count; i++)
1195
+ {
1196
+ float angleDeg = angleStep * i;
1197
+ float angleRad = angleDeg * Mathf.Deg2Rad;
1198
+
1199
+ Vector3 pos = new Vector3(
1200
+ Mathf.Sin(angleRad) * doorRadius,
1201
+ doorHeight,
1202
+ Mathf.Cos(angleRad) * doorRadius
1203
+ );
1204
+
1205
+ Quaternion rot = Quaternion.LookRotation(-pos.normalized, Vector3.up);
1206
+
1207
+ var doorObj = Instantiate(doorPrefab, pos, rot, transform);
1208
+ var door = doorObj.GetComponent<WorkspaceDoor>();
1209
+ door?.Initialize(workspaces[i]);
1210
+
1211
+ spawnedDoors.Add(doorObj);
1212
+ }
1213
+ }
1214
+
1215
+ public void EnterWorkspace(WorkspaceData workspace)
1216
+ {
1217
+ // Stop lobby polling
1218
+ if (pollCoroutine != null)
1219
+ {
1220
+ StopCoroutine(pollCoroutine);
1221
+ pollCoroutine = null;
1222
+ }
1223
+
1224
+ // Hide lobby objects
1225
+ foreach (var d in spawnedDoors) d.SetActive(false);
1226
+
1227
+ // Activate room
1228
+ roomManager?.LoadWorkspace(workspace);
1229
+ }
1230
+
1231
+ public void ReturnToLobby()
1232
+ {
1233
+ roomManager?.UnloadWorkspace();
1234
+ foreach (var d in spawnedDoors) d.SetActive(true);
1235
+ pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollWorkspaces());
1236
+ }
1237
+
1238
+ private void ShowOffline()
1239
+ {
1240
+ if (offlinePanel != null) offlinePanel.SetActive(true);
1241
+ }
1242
+
1243
+ private void HideOffline()
1244
+ {
1245
+ if (offlinePanel != null) offlinePanel.SetActive(false);
1246
+ }
1247
+ }
1248
+ ```
1249
+
1250
+ - [ ] **Step 4: Commit**
1251
+
1252
+ ```bash
1253
+ cd C:\projects\spaces-vr
1254
+ git add Assets/Scripts/Lobby/
1255
+ git commit -m "feat: add lobby with workspace doors and gaze-to-enter navigation"
1256
+ ```
1257
+
1258
+ ---
1259
+
1260
+ ### Task 7: Room Manager — Pane Spawning and Room Scaling
1261
+
1262
+ **Files:**
1263
+ - Create: `Assets/Scripts/Room/RoomManager.cs`
1264
+
1265
+ - [ ] **Step 1: Create RoomManager**
1266
+
1267
+ ```csharp
1268
+ // Assets/Scripts/Room/RoomManager.cs
1269
+ using System.Collections;
1270
+ using System.Collections.Generic;
1271
+ using UnityEngine;
1272
+
1273
+ public class RoomManager : MonoBehaviour
1274
+ {
1275
+ [SerializeField] private GameObject panePrefab;
1276
+ [SerializeField] private Light roomLight;
1277
+ [SerializeField] private Transform roomContainer;
1278
+
1279
+ private List<GameObject> spawnedPanes = new List<GameObject>();
1280
+ private WorkspaceData currentWorkspace;
1281
+ private Coroutine pollCoroutine;
1282
+
1283
+ // Room scaling light presets
1284
+ private static readonly Color WarmLight = new Color(1f, 0.9f, 0.8f);
1285
+ private static readonly Color NeutralLight = new Color(0.9f, 0.9f, 1f);
1286
+ private static readonly Color CoolLight = new Color(0.7f, 0.8f, 1f);
1287
+
1288
+ public void LoadWorkspace(WorkspaceData workspace)
1289
+ {
1290
+ currentWorkspace = workspace;
1291
+ if (roomContainer != null) roomContainer.gameObject.SetActive(true);
1292
+
1293
+ StartCoroutine(FetchAndSpawnPanes());
1294
+ }
1295
+
1296
+ public void UnloadWorkspace()
1297
+ {
1298
+ if (pollCoroutine != null)
1299
+ {
1300
+ StopCoroutine(pollCoroutine);
1301
+ pollCoroutine = null;
1302
+ }
1303
+
1304
+ foreach (var p in spawnedPanes) Destroy(p);
1305
+ spawnedPanes.Clear();
1306
+
1307
+ currentWorkspace = null;
1308
+ if (roomContainer != null) roomContainer.gameObject.SetActive(false);
1309
+ }
1310
+
1311
+ private IEnumerator FetchAndSpawnPanes()
1312
+ {
1313
+ yield return SpacesConnection.Instance.FetchPanes(currentWorkspace.id, SpawnPanes);
1314
+ pollCoroutine = StartCoroutine(SpacesConnection.Instance.PollPanes(currentWorkspace.id));
1315
+ }
1316
+
1317
+ private void SpawnPanes(List<PaneData> panes)
1318
+ {
1319
+ if (panes == null) return;
1320
+
1321
+ // Clear existing
1322
+ foreach (var p in spawnedPanes) Destroy(p);
1323
+ spawnedPanes.Clear();
1324
+
1325
+ // Compute positions
1326
+ var positions = PaneLayout.ComputePositions(panes.Count);
1327
+
1328
+ // Parse workspace color
1329
+ Color wsColor;
1330
+ if (!ColorUtility.TryParseHtmlString(currentWorkspace.color, out wsColor))
1331
+ wsColor = new Color(0.4f, 0.3f, 0.9f);
1332
+
1333
+ // Scale room lighting based on pane count
1334
+ SetRoomLighting(panes.Count);
1335
+
1336
+ // Spawn panes
1337
+ for (int i = 0; i < panes.Count; i++)
1338
+ {
1339
+ Vector3 pos = positions[i];
1340
+ Quaternion rot = PaneLayout.ComputeRotation(pos);
1341
+
1342
+ Transform parent = roomContainer != null ? roomContainer : transform;
1343
+ var paneObj = Instantiate(panePrefab, pos, rot, parent);
1344
+ var surface = paneObj.GetComponent<PaneSurface>();
1345
+ surface?.Initialize(panes[i], wsColor);
1346
+
1347
+ spawnedPanes.Add(paneObj);
1348
+ }
1349
+ }
1350
+
1351
+ private void SetRoomLighting(int paneCount)
1352
+ {
1353
+ if (roomLight == null) return;
1354
+
1355
+ if (paneCount <= 2)
1356
+ {
1357
+ roomLight.color = WarmLight;
1358
+ roomLight.intensity = 0.6f;
1359
+ }
1360
+ else if (paneCount <= 5)
1361
+ {
1362
+ roomLight.color = NeutralLight;
1363
+ roomLight.intensity = 0.8f;
1364
+ }
1365
+ else
1366
+ {
1367
+ roomLight.color = CoolLight;
1368
+ roomLight.intensity = 1.0f;
1369
+ }
1370
+ }
1371
+ }
1372
+ ```
1373
+
1374
+ - [ ] **Step 2: Commit**
1375
+
1376
+ ```bash
1377
+ cd C:\projects\spaces-vr
1378
+ git add Assets/Scripts/Room/RoomManager.cs
1379
+ git commit -m "feat: add room manager with pane spawning and adaptive lighting"
1380
+ ```
1381
+
1382
+ ---
1383
+
1384
+ ### Task 8: Palm Menu (Return to Lobby)
1385
+
1386
+ **Files:**
1387
+ - Create: `Assets/Scripts/Interaction/PalmMenu.cs`
1388
+
1389
+ - [ ] **Step 1: Create PalmMenu**
1390
+
1391
+ ```csharp
1392
+ // Assets/Scripts/Interaction/PalmMenu.cs
1393
+ using UnityEngine;
1394
+
1395
+ public class PalmMenu : MonoBehaviour
1396
+ {
1397
+ [SerializeField] private OVRHand hand; // left hand
1398
+ [SerializeField] private OVRSkeleton skeleton;
1399
+ [SerializeField] private GameObject menuPanel; // small floating UI
1400
+ [SerializeField] private Renderer homeButton;
1401
+ [SerializeField] private float palmUpThreshold = 0.7f; // dot product with up vector
1402
+ [SerializeField] private float gazeConfirmTime = 1.0f;
1403
+
1404
+ private bool menuVisible;
1405
+ private float gazeTimer;
1406
+ private bool isGazingHome;
1407
+ private MaterialPropertyBlock propBlock;
1408
+
1409
+ private void Awake()
1410
+ {
1411
+ propBlock = new MaterialPropertyBlock();
1412
+ if (menuPanel != null) menuPanel.SetActive(false);
1413
+ }
1414
+
1415
+ private void Update()
1416
+ {
1417
+ UpdateMenuVisibility();
1418
+ if (menuVisible) UpdateGazeConfirm();
1419
+ }
1420
+
1421
+ private void UpdateMenuVisibility()
1422
+ {
1423
+ if (hand == null || skeleton == null)
1424
+ {
1425
+ SetMenuVisible(false);
1426
+ return;
1427
+ }
1428
+
1429
+ // Check if palm is facing up
1430
+ if (skeleton.Bones == null || skeleton.Bones.Count == 0)
1431
+ {
1432
+ SetMenuVisible(false);
1433
+ return;
1434
+ }
1435
+
1436
+ // Use wrist bone orientation to determine palm facing
1437
+ Transform wrist = skeleton.Bones[(int)OVRSkeleton.BoneId.Hand_WristRoot]?.Transform;
1438
+ if (wrist == null) { SetMenuVisible(false); return; }
1439
+
1440
+ float palmDot = Vector3.Dot(wrist.up, Vector3.up);
1441
+ bool palmUp = palmDot > palmUpThreshold;
1442
+
1443
+ // Also check if user is looking at their palm
1444
+ bool lookingAtPalm = false;
1445
+ if (GazeManager.Instance?.LastHit != null)
1446
+ {
1447
+ float distToHand = Vector3.Distance(
1448
+ GazeManager.Instance.LastHit.Value.point, wrist.position);
1449
+ lookingAtPalm = distToHand < 0.2f;
1450
+ }
1451
+
1452
+ SetMenuVisible(palmUp && lookingAtPalm);
1453
+ }
1454
+
1455
+ private void SetMenuVisible(bool visible)
1456
+ {
1457
+ if (menuVisible == visible) return;
1458
+ menuVisible = visible;
1459
+ if (menuPanel != null) menuPanel.SetActive(visible);
1460
+ if (!visible) gazeTimer = 0f;
1461
+ }
1462
+
1463
+ private void UpdateGazeConfirm()
1464
+ {
1465
+ // Check if gaze is on the home button
1466
+ var focused = GazeManager.Instance?.FocusedObject;
1467
+ if (focused != null && focused == homeButton?.gameObject)
1468
+ {
1469
+ gazeTimer += Time.deltaTime;
1470
+
1471
+ // Update progress ring
1472
+ if (homeButton != null)
1473
+ {
1474
+ homeButton.GetPropertyBlock(propBlock);
1475
+ propBlock.SetFloat("_Progress", gazeTimer / gazeConfirmTime);
1476
+ homeButton.SetPropertyBlock(propBlock);
1477
+ }
1478
+
1479
+ if (gazeTimer >= gazeConfirmTime)
1480
+ {
1481
+ gazeTimer = 0f;
1482
+ ReturnToLobby();
1483
+ }
1484
+ }
1485
+ else
1486
+ {
1487
+ gazeTimer = 0f;
1488
+ }
1489
+ }
1490
+
1491
+ private void ReturnToLobby()
1492
+ {
1493
+ LobbyManager.Instance?.ReturnToLobby();
1494
+ }
1495
+ }
1496
+ ```
1497
+
1498
+ - [ ] **Step 2: Commit**
1499
+
1500
+ ```bash
1501
+ cd C:\projects\spaces-vr
1502
+ git add Assets/Scripts/Interaction/PalmMenu.cs
1503
+ git commit -m "feat: add palm menu with gaze-confirm return to lobby"
1504
+ ```
1505
+
1506
+ ---
1507
+
1508
+ ### Task 9: Scene Setup and Prefab Assembly (Unity Editor)
1509
+
1510
+ **Context:** This task is done entirely in the Unity Editor. It connects all the scripts to GameObjects and creates the prefabs and scenes.
1511
+
1512
+ - [ ] **Step 1: Create PaneSurface prefab**
1513
+
1514
+ 1. In Scene, create: 3D Object → Quad. Name it `PaneSurface`.
1515
+ 2. Scale: (1.2, 0.8, 1). This is the pane surface.
1516
+ 3. Create a dark material (`PaneSurface.mat`): Shader = URP/Lit, Base Color = `#0a0a0f`, Metallic = 0, Smoothness = 0.1.
1517
+ 4. Add a child: 3D Object → Sphere (scale 0.02) for the status dot. Position top-left corner.
1518
+ 5. Add child TextMeshPro objects for header (top of quad) and content (body).
1519
+ 6. Add components to the root:
1520
+ - `PaneSurface.cs`
1521
+ - `GazeFocusHighlight.cs`
1522
+ - `BoxCollider` (auto-added by PaneSurface RequireComponent)
1523
+ 7. Wire serialized fields: `contentText`, `header`, `statusIndicator` → child objects.
1524
+ 8. Drag to `Assets/Prefabs/PaneSurface.prefab`.
1525
+
1526
+ - [ ] **Step 2: Create WorkspaceDoor prefab**
1527
+
1528
+ 1. Create an empty GameObject named `WorkspaceDoor`.
1529
+ 2. Add child: Cube scaled to (1.2, 2.2, 0.1) — the door frame. Apply `DoorFrame.mat`.
1530
+ 3. Add child: TextMeshPro for name label (above frame).
1531
+ 4. Add child: TextMeshPro for info label (inside frame).
1532
+ 5. Add child: Point Light (range 2, intensity 0.5) for glow.
1533
+ 6. Add components to root:
1534
+ - `WorkspaceDoor.cs`
1535
+ - `DoorInteraction.cs`
1536
+ - `GazeFocusHighlight.cs`
1537
+ - `BoxCollider` (size matching the door frame)
1538
+ 7. Wire serialized fields.
1539
+ 8. Drag to `Assets/Prefabs/WorkspaceDoor.prefab`.
1540
+
1541
+ - [ ] **Step 3: Set up the main scene**
1542
+
1543
+ 1. Open the default scene (or create `Assets/Scenes/Main.unity`).
1544
+ 2. Ensure OVRCameraRig is present (from Task 1).
1545
+ 3. Create empty GameObjects:
1546
+ - `[Managers]` — add `SessionManager.cs`, `SpacesConnection.cs`, `GazeManager.cs`
1547
+ - `[Lobby]` — add `LobbyManager.cs`. Wire `doorPrefab` to the WorkspaceDoor prefab.
1548
+ - `[Room]` — add `RoomManager.cs`. Wire `panePrefab` to the PaneSurface prefab. Add a Directional Light child for room lighting.
1549
+ - `[Interaction]` — add `HandGrabHandler.cs`. Wire `leftHand` and `rightHand` to OVRHandPrefab instances.
1550
+ - `[PalmMenu]` — add `PalmMenu.cs` with a small floating Canvas child for the home button.
1551
+ 4. Add OVRHandPrefab for left and right hands (from Meta XR SDK samples).
1552
+ 5. Set Room container initially inactive.
1553
+ 6. Create an "Offline Panel" — a TextMeshPro world-space canvas at center, initially inactive. Wire to LobbyManager's `offlinePanel` field.
1554
+ 7. Wire cross-references: LobbyManager.roomManager → RoomManager, etc.
1555
+
1556
+ - [ ] **Step 4: Test in editor Play Mode**
1557
+
1558
+ Press Play in Unity Editor. With Meta XR Simulator:
1559
+ 1. Verify lobby spawns — should see "Offline" panel if no server is running.
1560
+ 2. Start Spaces server (`node bin/spaces.js --port 3457`). Hit Play again.
1561
+ 3. Doors should appear for each workspace.
1562
+ 4. Simulated gaze → door → should trigger confirm ring and enter room.
1563
+ 5. Panes should spawn in semicircle with correct names.
1564
+
1565
+ - [ ] **Step 5: Commit**
1566
+
1567
+ ```bash
1568
+ cd C:\projects\spaces-vr
1569
+ git add -A
1570
+ git commit -m "feat: assemble scenes, prefabs, and wire all components"
1571
+ ```
1572
+
1573
+ ---
1574
+
1575
+ ### Task 10: Quest 3 Build and Deploy
1576
+
1577
+ - [ ] **Step 1: Configure Android build**
1578
+
1579
+ In Unity:
1580
+ 1. File → Build Settings → ensure Android platform is selected
1581
+ 2. Player Settings → verify: IL2CPP, ARM64, Vulkan, API 32 (from Task 1)
1582
+ 3. XR Plug-in Management → verify Oculus checked
1583
+ 4. Meta XR → verify Eye Tracking + Hand Tracking enabled
1584
+
1585
+ - [ ] **Step 2: Build APK**
1586
+
1587
+ File → Build Settings → Build → save as `Build/SpacesVR.apk`
1588
+
1589
+ Expected: Build succeeds. First build takes 5-10 minutes.
1590
+
1591
+ - [ ] **Step 3: Deploy to Quest 3 (when you have one)**
1592
+
1593
+ Option A — Quest Link:
1594
+ 1. Connect Quest via USB-C or Air Link
1595
+ 2. In Unity: File → Build and Run (deploys and launches automatically)
1596
+
1597
+ Option B — SideQuest:
1598
+ 1. Install SideQuest on PC
1599
+ 2. Connect Quest via USB
1600
+ 3. Drag `Build/SpacesVR.apk` into SideQuest
1601
+ 4. On Quest: Unknown Sources → Spaces VR
1602
+
1603
+ - [ ] **Step 4: Commit final build config**
1604
+
1605
+ ```bash
1606
+ cd C:\projects\spaces-vr
1607
+ git add -A
1608
+ git commit -m "chore: finalize Quest 3 build configuration"
1609
+ ```
1610
+
1611
+ ---
1612
+
1613
+ ### Summary: File Map
1614
+
1615
+ ```
1616
+ C:\projects\spaces-vr\Assets\Scripts\
1617
+ ├── Core\
1618
+ │ ├── WorkspaceData.cs # Task 2 — data models + JSON helpers
1619
+ │ ├── SpacesConnection.cs # Task 2 — HTTP client + polling
1620
+ │ └── SessionManager.cs # Task 2 — auth token management
1621
+ ├── Interaction\
1622
+ │ ├── GazeManager.cs # Task 3 — eye tracking + head-gaze fallback
1623
+ │ ├── GazeFocusHighlight.cs # Task 3 — visual focus feedback
1624
+ │ ├── HandGrabHandler.cs # Task 5 — grab/move/resize
1625
+ │ └── PalmMenu.cs # Task 8 — palm-up home button
1626
+ ├── Lobby\
1627
+ │ ├── LobbyManager.cs # Task 6 — spawn doors, manage transitions
1628
+ │ ├── WorkspaceDoor.cs # Task 6 — door rendering
1629
+ │ └── DoorInteraction.cs # Task 6 — gaze-to-enter
1630
+ ├── Room\
1631
+ │ ├── RoomManager.cs # Task 7 — spawn panes, room lighting
1632
+ │ ├── PaneSurface.cs # Task 4 — pane placeholder
1633
+ │ └── PaneLayout.cs # Task 4 — semicircle math
1634
+ └── UI\
1635
+ ├── PaneHeader.cs # Task 4 — agent name/type label
1636
+ └── StatusIndicator.cs # Task 4 — colored status dot
1637
+
1638
+ C:\projects\spaces\src\app\api\panes\route.ts # Task 0 — workspace_id filter
1639
+ ```