codemble 0.5.3__tar.gz → 0.6.1__tar.gz

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 (242) hide show
  1. {codemble-0.5.3 → codemble-0.6.1}/.gitignore +1 -0
  2. {codemble-0.5.3 → codemble-0.6.1}/CHANGELOG.md +80 -0
  3. {codemble-0.5.3 → codemble-0.6.1}/CLAUDE.md +92 -9
  4. codemble-0.6.1/CONTEXT.md +28 -0
  5. {codemble-0.5.3 → codemble-0.6.1}/PKG-INFO +8 -1
  6. {codemble-0.5.3 → codemble-0.6.1}/README.md +7 -0
  7. {codemble-0.5.3 → codemble-0.6.1}/codemble/__init__.py +1 -1
  8. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/base.py +8 -1
  9. {codemble-0.5.3 → codemble-0.6.1}/codemble/graph/layout.py +158 -32
  10. {codemble-0.5.3 → codemble-0.6.1}/codemble/graph/mapview.py +196 -8
  11. {codemble-0.5.3 → codemble-0.6.1}/codemble/server/app.py +69 -241
  12. codemble-0.6.1/codemble/server/project_activation.py +212 -0
  13. codemble-0.6.1/codemble/server/project_selection.py +117 -0
  14. codemble-0.5.3/codemble/web_dist/assets/index-DTjcBtZj.js → codemble-0.6.1/codemble/web_dist/assets/index-0OmvUbwQ.js +1 -1
  15. codemble-0.6.1/codemble/web_dist/assets/index-CsUbeE2C.css +1 -0
  16. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/index.html +2 -2
  17. codemble-0.6.1/docs/plans/2026-07-20-oss-inspired-map-and-constellations.md +417 -0
  18. codemble-0.6.1/docs/releases/v0.6.1.md +39 -0
  19. codemble-0.6.1/docs/research/2026-07-20-opensource-graph-architecture-inspiration.md +192 -0
  20. codemble-0.6.1/docs/superpowers/specs/2026-07-21-navigation-design-clarity-design.md +183 -0
  21. {codemble-0.5.3 → codemble-0.6.1}/docs-site/package.json +1 -1
  22. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/star-chart.md +4 -0
  23. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/the-galaxy.md +16 -6
  24. {codemble-0.5.3 → codemble-0.6.1}/pyproject.toml +1 -1
  25. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/check_suites.json +36 -36
  26. codemble-0.6.1/tests/test_graph_finalization.py +277 -0
  27. {codemble-0.5.3 → codemble-0.6.1}/tests/test_mapview.py +190 -3
  28. codemble-0.6.1/tests/test_project_activation.py +68 -0
  29. codemble-0.6.1/tests/test_project_selection.py +71 -0
  30. {codemble-0.5.3 → codemble-0.6.1}/tests/test_python_ast.py +2 -2
  31. {codemble-0.5.3 → codemble-0.6.1}/tests/test_server.py +5 -1
  32. {codemble-0.5.3 → codemble-0.6.1}/web/package-lock.json +2 -2
  33. {codemble-0.5.3 → codemble-0.6.1}/web/package.json +3 -2
  34. codemble-0.6.1/web/scripts/benchmark_learner_projection.mjs +114 -0
  35. {codemble-0.5.3 → codemble-0.6.1}/web/scripts/check_graph_data.mjs +130 -0
  36. codemble-0.6.1/web/scripts/check_learner_projection.mjs +165 -0
  37. {codemble-0.5.3 → codemble-0.6.1}/web/scripts/check_learner_session.mjs +104 -0
  38. codemble-0.6.1/web/scripts/check_name_atlas.mjs +75 -0
  39. codemble-0.6.1/web/scripts/check_orbit_pointer_guard.mjs +34 -0
  40. codemble-0.6.1/web/scripts/check_project_mapping.mjs +189 -0
  41. {codemble-0.5.3 → codemble-0.6.1}/web/src/App.jsx +412 -137
  42. {codemble-0.5.3 → codemble-0.6.1}/web/src/GalaxyCanvas.jsx +141 -20
  43. {codemble-0.5.3 → codemble-0.6.1}/web/src/GuidanceLayer.jsx +4 -4
  44. {codemble-0.5.3 → codemble-0.6.1}/web/src/MapView.jsx +237 -12
  45. {codemble-0.5.3 → codemble-0.6.1}/web/src/galaxyMaterials.js +86 -0
  46. {codemble-0.5.3 → codemble-0.6.1}/web/src/graphData.js +189 -23
  47. codemble-0.6.1/web/src/learnerProjection.js +212 -0
  48. {codemble-0.5.3 → codemble-0.6.1}/web/src/learnerSession.js +93 -372
  49. codemble-0.6.1/web/src/nameAtlas.js +177 -0
  50. codemble-0.6.1/web/src/orbitPointerGuard.js +40 -0
  51. codemble-0.6.1/web/src/projectMapping.js +344 -0
  52. {codemble-0.5.3 → codemble-0.6.1}/web/src/styles.css +547 -18
  53. {codemble-0.5.3 → codemble-0.6.1}/web/src/tokens.css +13 -0
  54. codemble-0.5.3/codemble/web_dist/assets/index-Dbk76SAB.css +0 -1
  55. codemble-0.5.3/tests/test_graph_finalization.py +0 -121
  56. {codemble-0.5.3 → codemble-0.6.1}/.env.example +0 -0
  57. {codemble-0.5.3 → codemble-0.6.1}/.gitattributes +0 -0
  58. {codemble-0.5.3 → codemble-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  59. {codemble-0.5.3 → codemble-0.6.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  60. {codemble-0.5.3 → codemble-0.6.1}/.github/ISSUE_TEMPLATE/early_tester.yml +0 -0
  61. {codemble-0.5.3 → codemble-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  62. {codemble-0.5.3 → codemble-0.6.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  63. {codemble-0.5.3 → codemble-0.6.1}/.github/dependabot.yml +0 -0
  64. {codemble-0.5.3 → codemble-0.6.1}/.github/workflows/ci.yml +0 -0
  65. {codemble-0.5.3 → codemble-0.6.1}/.github/workflows/pages.yml +0 -0
  66. {codemble-0.5.3 → codemble-0.6.1}/.github/workflows/publish-pypi.yml +0 -0
  67. {codemble-0.5.3 → codemble-0.6.1}/AGENTS.md +0 -0
  68. {codemble-0.5.3 → codemble-0.6.1}/CODE_OF_CONDUCT.md +0 -0
  69. {codemble-0.5.3 → codemble-0.6.1}/CONTRIBUTING.md +0 -0
  70. {codemble-0.5.3 → codemble-0.6.1}/LICENSE +0 -0
  71. {codemble-0.5.3 → codemble-0.6.1}/NOTICE +0 -0
  72. {codemble-0.5.3 → codemble-0.6.1}/SECURITY.md +0 -0
  73. {codemble-0.5.3 → codemble-0.6.1}/TESTING.md +0 -0
  74. {codemble-0.5.3 → codemble-0.6.1}/assets/demo.gif +0 -0
  75. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/__init__.py +0 -0
  76. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/discovery.py +0 -0
  77. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/parse_progress.py +0 -0
  78. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/project.py +0 -0
  79. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/python_ast.py +0 -0
  80. {codemble-0.5.3 → codemble-0.6.1}/codemble/adapters/typescript_tree_sitter.py +0 -0
  81. {codemble-0.5.3 → codemble-0.6.1}/codemble/checks/__init__.py +0 -0
  82. {codemble-0.5.3 → codemble-0.6.1}/codemble/checks/service.py +0 -0
  83. {codemble-0.5.3 → codemble-0.6.1}/codemble/cli.py +0 -0
  84. {codemble-0.5.3 → codemble-0.6.1}/codemble/graph/__init__.py +0 -0
  85. {codemble-0.5.3 → codemble-0.6.1}/codemble/graph/finalize.py +0 -0
  86. {codemble-0.5.3 → codemble-0.6.1}/codemble/lens/__init__.py +0 -0
  87. {codemble-0.5.3 → codemble-0.6.1}/codemble/lens/javascript_typescript.py +0 -0
  88. {codemble-0.5.3 → codemble-0.6.1}/codemble/lens/python.py +0 -0
  89. {codemble-0.5.3 → codemble-0.6.1}/codemble/llm/__init__.py +0 -0
  90. {codemble-0.5.3 → codemble-0.6.1}/codemble/llm/local_status.py +0 -0
  91. {codemble-0.5.3 → codemble-0.6.1}/codemble/llm/providers.py +0 -0
  92. {codemble-0.5.3 → codemble-0.6.1}/codemble/llm/structural.py +0 -0
  93. {codemble-0.5.3 → codemble-0.6.1}/codemble/llm/study.py +0 -0
  94. {codemble-0.5.3 → codemble-0.6.1}/codemble/paths.py +0 -0
  95. {codemble-0.5.3 → codemble-0.6.1}/codemble/progress/__init__.py +0 -0
  96. {codemble-0.5.3 → codemble-0.6.1}/codemble/progress/store.py +0 -0
  97. {codemble-0.5.3 → codemble-0.6.1}/codemble/server/__init__.py +0 -0
  98. {codemble-0.5.3 → codemble-0.6.1}/codemble/server/parse_job.py +0 -0
  99. {codemble-0.5.3 → codemble-0.6.1}/codemble/server/runtime.py +0 -0
  100. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  101. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  102. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  103. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  104. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff +0 -0
  105. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2 +0 -0
  106. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff +0 -0
  107. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2 +0 -0
  108. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2 +0 -0
  109. {codemble-0.5.3 → codemble-0.6.1}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff +0 -0
  110. {codemble-0.5.3 → codemble-0.6.1}/docs/adr/README.md +0 -0
  111. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/2026-07-19-audience-modes-and-local-narration-design.md +0 -0
  112. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/2026-07-19-audience-modes-backend-plan.md +0 -0
  113. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/2026-07-19-audience-modes-frontend-plan.md +0 -0
  114. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/2026-07-19-install-ux-folder-picker-design.md +0 -0
  115. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/2026-07-19-install-ux-folder-picker-plan.md +0 -0
  116. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/README.md +0 -0
  117. {codemble-0.5.3 → codemble-0.6.1}/docs/plans/phase-1.md +0 -0
  118. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/checklist.md +0 -0
  119. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.1.0.md +0 -0
  120. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.2.0.md +0 -0
  121. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.4.0.md +0 -0
  122. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.5.0.md +0 -0
  123. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.5.1.md +0 -0
  124. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.5.2.md +0 -0
  125. {codemble-0.5.3 → codemble-0.6.1}/docs/releases/v0.5.3.md +0 -0
  126. {codemble-0.5.3 → codemble-0.6.1}/docs/research/README.md +0 -0
  127. {codemble-0.5.3 → codemble-0.6.1}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-a.md +0 -0
  128. {codemble-0.5.3 → codemble-0.6.1}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-b.md +0 -0
  129. {codemble-0.5.3 → codemble-0.6.1}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-c.md +0 -0
  130. {codemble-0.5.3 → codemble-0.6.1}/docs/superpowers/plans/2026-07-19-galaxy-ux-shared-contract.md +0 -0
  131. {codemble-0.5.3 → codemble-0.6.1}/docs/superpowers/specs/2026-07-19-galaxy-ux-overhaul-design.md +0 -0
  132. {codemble-0.5.3 → codemble-0.6.1}/docs-site/.gitignore +0 -0
  133. {codemble-0.5.3 → codemble-0.6.1}/docs-site/astro.config.mjs +0 -0
  134. {codemble-0.5.3 → codemble-0.6.1}/docs-site/design.md +0 -0
  135. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/favicon-16.png +0 -0
  136. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/favicon-32.png +0 -0
  137. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/icon-180.png +0 -0
  138. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/icon-192.png +0 -0
  139. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/icon-512.png +0 -0
  140. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/icon.svg +0 -0
  141. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/icons/asterism.svg +0 -0
  142. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/icons/install.svg +0 -0
  143. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/mark-animated.svg +0 -0
  144. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/mark.svg +0 -0
  145. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/hero-chart.svg +0 -0
  146. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/hero-field.svg +0 -0
  147. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/hero-gold.svg +0 -0
  148. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/kasumi-rule.svg +0 -0
  149. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/plate-galaxy.svg +0 -0
  150. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/plate-study.svg +0 -0
  151. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/plate-system.svg +0 -0
  152. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/brand/plates/seal.svg +0 -0
  153. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/favicon.svg +0 -0
  154. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/easy-mode.png +0 -0
  155. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/galaxy-lit.png +0 -0
  156. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/galaxy.png +0 -0
  157. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/loading.png +0 -0
  158. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/map-architecture.png +0 -0
  159. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/map-workflow.png +0 -0
  160. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/study-panel.png +0 -0
  161. {codemble-0.5.3 → codemble-0.6.1}/docs-site/public/shots/system.png +0 -0
  162. {codemble-0.5.3 → codemble-0.6.1}/docs-site/scripts/build-plates.mjs +0 -0
  163. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/assets/codemble-mark-dark.svg +0 -0
  164. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/assets/codemble-mark-light.svg +0 -0
  165. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/components/Search.astro +0 -0
  166. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/components/Tatebanko.astro +0 -0
  167. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/architecture.md +0 -0
  168. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/build-from-source.md +0 -0
  169. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/checks-and-lighting.md +0 -0
  170. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/contributing.md +0 -0
  171. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/correctness.md +0 -0
  172. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/early-testing.md +0 -0
  173. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/installation.md +0 -0
  174. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/introduction.md +0 -0
  175. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m1-parser.md +0 -0
  176. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m10-polyglot-release.md +0 -0
  177. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m12-galaxy-look.md +0 -0
  178. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m13-scale.md +0 -0
  179. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m2-galaxy.md +0 -0
  180. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m3-study.md +0 -0
  181. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m4-lens.md +0 -0
  182. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m5-checks.md +0 -0
  183. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m6-release.md +0 -0
  184. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m8-typescript.md +0 -0
  185. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/progress/m9-typescript-lens.md +0 -0
  186. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/quickstart.md +0 -0
  187. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/roadmap.md +0 -0
  188. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content/docs/study-panel.md +0 -0
  189. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/content.config.ts +0 -0
  190. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/pages/index.astro +0 -0
  191. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/styles/custom.css +0 -0
  192. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/styles/landing.css +0 -0
  193. {codemble-0.5.3 → codemble-0.6.1}/docs-site/src/styles/tokens.css +0 -0
  194. {codemble-0.5.3 → codemble-0.6.1}/scripts/dev.sh +0 -0
  195. {codemble-0.5.3 → codemble-0.6.1}/scripts/record_demo.sh +0 -0
  196. {codemble-0.5.3 → codemble-0.6.1}/tests/conftest.py +0 -0
  197. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/concepts_sample.py +0 -0
  198. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/impact/alpha.py +0 -0
  199. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/impact/beta.py +0 -0
  200. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/impact/gamma.py +0 -0
  201. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/impact/helpers.py +0 -0
  202. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/impact/noisy.py +0 -0
  203. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/python_worker.py +0 -0
  204. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/broken.ts +0 -0
  205. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/legacy.js +0 -0
  206. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/local.js +0 -0
  207. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/main.ts +0 -0
  208. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/reexport.ts +0 -0
  209. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/util.ts +0 -0
  210. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/polyglot/src/widget.tsx +0 -0
  211. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/repeated_calls.py +0 -0
  212. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/.gitignore +0 -0
  213. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/ambiguous.py +0 -0
  214. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/api.py +0 -0
  215. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/app.py +0 -0
  216. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/broken.py +0 -0
  217. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/cli.py +0 -0
  218. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/pkg/__init__.py +0 -0
  219. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/pkg/helpers.py +0 -0
  220. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/pkg/service.py +0 -0
  221. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/pkg/util.py +0 -0
  222. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/runner/__main__.py +0 -0
  223. {codemble-0.5.3 → codemble-0.6.1}/tests/fixtures/sampleproj/shared.py +0 -0
  224. {codemble-0.5.3 → codemble-0.6.1}/tests/test_checks.py +0 -0
  225. {codemble-0.5.3 → codemble-0.6.1}/tests/test_dependency_compatibility.py +0 -0
  226. {codemble-0.5.3 → codemble-0.6.1}/tests/test_parse_job.py +0 -0
  227. {codemble-0.5.3 → codemble-0.6.1}/tests/test_parse_progress.py +0 -0
  228. {codemble-0.5.3 → codemble-0.6.1}/tests/test_progress.py +0 -0
  229. {codemble-0.5.3 → codemble-0.6.1}/tests/test_project_parser.py +0 -0
  230. {codemble-0.5.3 → codemble-0.6.1}/tests/test_providers.py +0 -0
  231. {codemble-0.5.3 → codemble-0.6.1}/tests/test_smoke.py +0 -0
  232. {codemble-0.5.3 → codemble-0.6.1}/tests/test_structural.py +0 -0
  233. {codemble-0.5.3 → codemble-0.6.1}/tests/test_study.py +0 -0
  234. {codemble-0.5.3 → codemble-0.6.1}/tests/test_typescript_tree_sitter.py +0 -0
  235. {codemble-0.5.3 → codemble-0.6.1}/web/README.md +0 -0
  236. {codemble-0.5.3 → codemble-0.6.1}/web/index.html +0 -0
  237. {codemble-0.5.3 → codemble-0.6.1}/web/scripts/record_demo.mjs +0 -0
  238. {codemble-0.5.3 → codemble-0.6.1}/web/src/ModeControl.jsx +0 -0
  239. {codemble-0.5.3 → codemble-0.6.1}/web/src/StudyPanel.jsx +0 -0
  240. {codemble-0.5.3 → codemble-0.6.1}/web/src/galaxyEffects.js +0 -0
  241. {codemble-0.5.3 → codemble-0.6.1}/web/src/main.jsx +0 -0
  242. {codemble-0.5.3 → codemble-0.6.1}/web/vite.config.js +0 -0
@@ -34,3 +34,4 @@ graphify-out/
34
34
 
35
35
  # Subagent-driven-development scratch coordination (not product truth)
36
36
  .superpowers/
37
+ .remember
@@ -5,6 +5,86 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.6.1] - 2026-07-21
9
+
10
+ ### Fixed
11
+ - **The full learning loop now fits a 320 px window.** A compact Menu replaces
12
+ the crowded action rail, the architecture Map waits for real layout
13
+ dimensions before fitting, guidance gets its own row instead of covering Map
14
+ controls, and Study becomes a full-stage reading sheet. The header, canvas,
15
+ guidance, and local-only status remain visible without horizontal overflow.
16
+ - **Modules and Find work from the Star chart.** Both global commands now render
17
+ their surface in chart mode instead of accepting a click into hidden state
18
+ that only appeared after returning to the galaxy.
19
+ - **First-run guidance no longer competes with Home calibration.** After an
20
+ audience is chosen, a required Home choice finishes before the three-step
21
+ coach opens, so there is one decision in the foreground at a time.
22
+ - **Canvas clicks no longer corrupt OrbitControls pointer state.** Codemble's
23
+ parser-owned node layout is now explicitly non-draggable, preventing the 3D
24
+ dependency's drag-end bridge from injecting a synthetic touch release into a
25
+ mouse release. A stale-pointer guard and focused contract pin the fallback.
26
+
27
+ ## [0.6.0] - 2026-07-21
28
+
29
+ ### Added
30
+ - **You can move the camera.** Dragging now orbits around whatever you are
31
+ looking at, and the wheel zooms. Panning stays off and both rotation and zoom
32
+ are clamped per level, so the thing you are studying can never leave the
33
+ screen — "you cannot get lost" still holds. Changing level is now click,
34
+ Enter, Escape, or the breadcrumb, because one gesture cannot mean both zoom
35
+ and travel.
36
+ - **Systems have names.** The most important stars carry their filename on the
37
+ canvas — Home first, then modules you have proved, then the ones most of your
38
+ project calls — with more names appearing as you zoom in and none allowed to
39
+ overlap another.
40
+ - **Find any module by name.** `⌘K` / `Ctrl-K` opens a filter-as-you-type jump
41
+ list, and a new Modules rail lists every file grouped by the import
42
+ communities the galaxy already places together. Both reach every module,
43
+ including ones the sky has not charted yet.
44
+
45
+ ### Changed
46
+ - Project selection, project activation, project mapping, the galaxy Name
47
+ Atlas, and the Learner Projection now each sit behind one focused module
48
+ interface. The HTTP API, parser graph, checks, persistence, and learner-visible
49
+ interactions are unchanged; focused contract suites pin each new seam.
50
+ - Learner projections are now dependency-scoped and indexed instead of being
51
+ recomputed on every session commit. A 1,000-node hover benchmark dropped from
52
+ about 0.331 ms to 0.001 ms per commit, and hover-only commits preserve the
53
+ identity of every unaffected graph, map, chart, reveal, hint, and module-index
54
+ projection so the 3D renderer does not rebuild stable scene data.
55
+ - **The galaxy no longer draws your whole project at once.** It opens on Home
56
+ and everything within two import routes of it; proving a module reveals what
57
+ it connects to, so the map is uncovered by understanding. Modules you have not
58
+ reached are still drawn — faint, unnamed, and without their routes — so the
59
+ project's real size is never misreported, and they stay clickable. A
60
+ **Show all** toggle draws everything whenever you want it. On a 90-system
61
+ project this is 22 systems and their routes instead of 90 and theirs.
62
+ - **The canvas got its stage back.** The display-size heading that covered the
63
+ left half of the view is now one line of text (`90 systems · 22 charted`), the
64
+ module path at system level lives in the breadcrumb instead of wrapping across
65
+ three lines, and the twelve-row legend is behind a **Key** button.
66
+ - **Map boxes say which module they are.** A box is a fixed width, and it used
67
+ to truncate the dotted module id — so `codemble.server.app` and
68
+ `codemble.server.runtime` both read `codemble.server…`, two different modules
69
+ showing identical text. Boxes are now named by the tail of their real path
70
+ (`server/app.py`, `server/runtime.py`); the full identifier is still in the
71
+ tooltip and read out to screen readers.
72
+ - **You can zoom and pan the Map.** The architecture diagram is far taller than
73
+ the window — on this project, nine import layers of which about four were
74
+ visible. Zoom in and out, drag empty space to pan, or press **Fit** to see the
75
+ whole shape at once. Every coordinate is still the one the backend computed;
76
+ zoom only changes how large it is drawn.
77
+ - The 2D Architecture map is now a deterministic layered diagram rather than
78
+ a straight-line grid: four barycenter sweeps reduce import-edge crossings,
79
+ backend-assigned ports keep fan-out distinct, weighted arrowed paths expose
80
+ direction, and cycles or long routes travel around the diagram flank. Possible
81
+ relationships remain dashed, and React still consumes graph-owned geometry.
82
+ - Galaxy systems now form deterministic constellations from parser-proven
83
+ import communities instead of scattering solely by identifier hash. The
84
+ additive `community` region field is exposed in graph schema 5 for later
85
+ renderers, while progress, Home, and system-internal orbital layout stay
86
+ unchanged.
87
+
8
88
  ## [0.5.3] - 2026-07-21
9
89
 
10
90
  ### Fixed
@@ -167,13 +167,29 @@ Polish, then the coordinated launch (Show HN / X; lit-galaxy GIF as hero).
167
167
 
168
168
  ## Current State **[AGENT-MAINTAINED]**
169
169
 
170
- **Current milestone: Phase 1 tester evidence** · Last updated: 2026-07-20 ·
171
- Session note: architecture-deepening maintenance is complete after the verified
172
- v0.2.0 release; all four report recommendations are merged in phases while issue
173
- #13 remains open for human tester evidence. The public site was then redesigned
174
- to the Formal Edo palette and Edo star-atlas genre, with an expanding Pagefind
175
- search shared by the landing and docs; no parser, graph, checks, persistence, or
176
- app behaviour was touched. A tester-run rehearsal of the shipped loop then
170
+ **Current milestone: Phase 1 tester evidence** · Last updated: 2026-07-21 ·
171
+ Session note: v0.6.1 closes the four verified user-flow gaps from the
172
+ 2026-07-21 usability audit: the 320 px shell reserves a usable map/study stage,
173
+ Modules and Find render from the Star chart, required Home calibration finishes
174
+ before coach marks begin, and parser-owned 3D nodes no longer install drag
175
+ controls that can corrupt OrbitControls pointer state. The release is verified
176
+ against the real bundled app at desktop and 320 px widths; the milestone does
177
+ not advance because issue #13 still requires human tester evidence. The v0.6.0
178
+ architecture-depth pass is complete in five
179
+ behavior-preserving waves: project selection owns the home-jailed filesystem
180
+ policy; project activation atomically owns parse-to-live binding and graph/map
181
+ caches; project mapping owns picker attempts, polling, retry, outage, stale
182
+ responses, and release; the Name Atlas owns deterministic plate placement; and
183
+ the indexed Learner Projection reuses every unaffected derived view. The
184
+ 1,000-node hover benchmark moved from ~0.331 ms to ~0.001 ms per commit, while
185
+ the HTTP, graph, check, persistence, and UI contracts stayed fixed. The current
186
+ milestone does not advance: issue #13 still requires human tester evidence.
187
+ Earlier architecture-deepening maintenance completed after the verified v0.2.0
188
+ release; all four report recommendations merged in phases. The public site was
189
+ then redesigned to the Formal Edo palette and Edo star-atlas genre, with an
190
+ expanding Pagefind search shared by the landing and docs; no parser, graph,
191
+ checks, persistence, or app behaviour was touched. A tester-run rehearsal of
192
+ the shipped loop then
177
193
  verified Home calibration, study source, checks, illumination, and restart
178
194
  persistence end to end, and found one real defect: multi-answer checks with four
179
195
  or more answers offered no wrong option, so select-all lit a region without
@@ -230,7 +246,42 @@ rebind a released project. Phase C plus that gap-fix wave shipped as **v0.5.0**;
230
246
  the parse work collided with an independent implementation of the same three
231
247
  foundational commits on `main`, reconciled by taking the branch's verified
232
248
  superset while preserving main's unique `CODEMBLE_DATA_DIR`/config-isolation
233
- fix, which lived in files the branch never touched.
249
+ fix, which lived in files the branch never touched. The Architecture map now
250
+ uses deterministic barycenter ordering and backend-routed, directional,
251
+ weight-scaled SVG paths; cycle and long-span routes use clear flank corridors,
252
+ while possible relationships remain dashed and React remains a pure renderer.
253
+ Galaxy regions now place in deterministic constellations derived only from
254
+ parser-proven import communities, with the community ID exposed in graph schema
255
+ 5 and progress signatures remaining coordinate-independent. A tester then
256
+ reported the 169-system galaxy unnavigable and undifferentiated, which resolved
257
+ into four separate defects: the camera could not move at all, no star carried a
258
+ name and there was no search or index, every region route drew unconditionally
259
+ so the mesh outshone the stars, and a display-size heading plus a twelve-row
260
+ always-on legend covered the stage. All four are fixed — bounded orbit,
261
+ progressive reveal keyed to a new `hops_from_home` graph field (schema 6),
262
+ ranked and decluttered name plates, a command palette plus an index sidebar over
263
+ one shared module index, and chrome demoted to a single line with the legend
264
+ behind a disclosure. On this repository the default galaxy went from 90 systems
265
+ with their whole route mesh to 22 charted with the rest drawn faint, unnamed and
266
+ edgeless; nothing was removed from the graph and no region re-dimmed. Four
267
+ defects were caught by running it rather than by the suite: a sprite map cleared
268
+ by an effect that ran after the one that filled it, an undefined constant that
269
+ threw inside the declutter timer and silently erased every name, plates that
270
+ claimed one screen cell regardless of their real width, and an open sidebar
271
+ occluding the system panel's primary action. A fifth followed: labels offered
272
+ only one position each, directly above their star, so at galaxy zoom nearly
273
+ every plate lost its slot to a neighbour and a 90-system sky carried one name.
274
+ Names now try a short list of slots around the star and collision-test where the
275
+ plate actually draws rather than where its star sits — 1 name became roughly 24
276
+ with everything shown, 9 by default. The same navigation and clarity pass was
277
+ then applied to the Map layer, where two of the three galaxy problems turned out
278
+ to exist in a sharper form: a fixed-width box truncated the dotted region id, so
279
+ `codemble.server.app` and `codemble.server.runtime` both rendered as
280
+ `codemble.server…` — identical text for different modules — and a 960x2640
281
+ diagram sat in a plain scroll box showing four of its nine import layers. Boxes
282
+ are now named by the tail of their real path (map schema 3, zero visible-text
283
+ collisions across all 90 boxes on this repository) and the Map gained zoom, Fit,
284
+ and drag-to-pan. Progressive reveal was deliberately not extended to the Map.
234
285
 
235
286
  ### M0 — Repo, docs & website scaffold ✅ (2026-07-19)
236
287
  - [x] Root: README, LICENSE (Apache-2.0), CoC, SECURITY, CONTRIBUTING,
@@ -396,6 +447,23 @@ interactive galaxy; re-fetching the graph does not re-sort the world; the scale
396
447
  prompt is actionable entirely in-app; generated check suites are byte-identical
397
448
  to before the index change.
398
449
 
450
+ ### M14 — Architecture depth and indexed learner views ✅ (2026-07-21)
451
+ - [x] Put canonical browse-root resolution, folder listing, and recent-project
452
+ filtering behind `ProjectSelector`
453
+ - [x] Make parse-to-live binding, stale-worker refusal, release, and graph/map
454
+ cache lifetime atomic behind `ProjectActivation`
455
+ - [x] Put picker attempts, parse polling/backoff, retry, outage, reset, and
456
+ stale-response guards behind one Project Mapping Run
457
+ - [x] Put name ranking, camera budget, projection, slots, collision cells,
458
+ sprite metadata, and cleanup behind one deterministic Name Atlas
459
+ - [x] Index learner projections by their real dependencies and prove hover-only
460
+ commits reuse stable outputs; benchmark the 1,000-node case
461
+
462
+ **Acceptance:** public HTTP payloads and parser/check/persistence contracts are
463
+ unchanged; focused module suites and the existing end-to-end session/server
464
+ suites pass; the production SPA is rebuilt; the 1,000-node projection benchmark
465
+ shows lower repeated-commit work without changing derived values.
466
+
399
467
  ## Decision Log **[AGENT-MAINTAINED — append only]**
400
468
 
401
469
  | Date | Decision | Why |
@@ -462,10 +530,25 @@ to before the index change.
462
530
  | 2026-07-20 | Progress reporting is a thread-scoped per-file hook (`note_file_parsed`) bound by `ProjectParser`, not a new `LanguageAdapter` parameter | The public adapter seam must stay unchanged for Phase 2 languages; one hook site per adapter also gives cancellation its exact "between files" meaning |
463
531
  | 2026-07-20 | Phase C adds `DELETE /api/progress`, the `CLEAR_PROGRESS` session event, and a `clearProgress` adapter method beyond the shared contract's Phase C rows | The contract's Phase C rows covered parse progress only, while the no-reset-progress-control gap is mapped to Phase C by the spec; recorded here rather than silently widened |
464
532
  | 2026-07-20 | Generated check suites are pinned by a committed golden fixture before any performance work touches `checks/service.py` | The Correctness Contract makes suite drift top-severity, and a refactor that changes an answer is invisible without a byte-level pin |
533
+ | 2026-07-20 | Architecture map edges get backend-computed ports, barycenter ordering, arrowheads, and weight-scaled strokes; `MAP_SCHEMA_VERSION` 2; directory groups stay payload metadata | Within-layer order was arbitrary and direction was invisible in 2D while being parser truth; ordering stays deterministic (fixed sweeps, sorted ties); group containers wait for hierarchical layout |
534
+ | 2026-07-20 | Galaxy regions place by deterministic import-community constellations (pure-Python label propagation in `layout.py`); `community` is an additive Region field; layout bytes change once | Hash-order placement scattered coupled modules; communities are parser-truth-derived and deterministic; progress signatures hash file content so nothing re-dims (M12 precedent) |
535
+ | 2026-07-21 | **Bounded orbit** replaces the fixed camera, amending the free-flight Non-Goal: `controlType('orbit')` with panning disabled, per-level distance clamps, and clamped polar angle. The wheel becomes zoom; level changes move to click/Enter/Escape/breadcrumb | Approved by UD after a tester reported the galaxy unnavigable. Panning is the one degree of freedom that can strand a learner in empty space with nothing to navigate back by, so it stays off — rotation and zoom are clamped instead, which keeps "you cannot get lost" true. One gesture cannot mean both zoom and change-level, so the wheel's old meaning had to move |
536
+ | 2026-07-21 | Galaxy uses **progressive reveal**: floor (within 2 import hops of Home) ∪ neighbours of every lit region ∪ the current selection's neighbours, with a persisted Show-all toggle. An unrevealed region is drawn faint, unnamed, edgeless — never removed | Approved by UD. 169 systems and their whole route mesh was the hairball; dropping the *edges* of what is not yet charted thins the sky without a separate density control. Regions stay drawn and clickable because hiding one would misreport the project's size, which is precisely the kind of wrong a learner cannot detect. Reveal is recomputed from proven progress, never stored, so it cannot drift out of step with it |
537
+ | 2026-07-21 | `Region.hops_from_home` is graph-layer truth (schema 6): undirected BFS from Home over proven import routes, `None` when unreachable; `with_entrypoint` recomputes it | Reveal is game logic and belongs in `LearnerSession`, but the *distance* is a fact about the project and belongs in the graph. The frontend was already re-walking this exact BFS for the Easy-mode hint, so the two could in principle have disagreed about one number; there is now one source. `None` is never softened to a large number, or "unreachable" would read as "very far" |
538
+ | 2026-07-21 | Canvas name plates are ranked (Home → lit → centrality), budgeted by camera distance, and decluttered by claiming the full screen-cell rectangle each plate covers | A name is the cheapest differentiation there is and the sky had none. Claiming one cell per plate let a wide name cover three neighbours, and claiming only a row let two plates straddling a boundary collide — the rectangle is the only version that actually holds. Plate geometry is published on the sprite by the module that sizes it, so the constant is not duplicated across files |
539
+ | 2026-07-21 | Finding a module is a command palette **and** an index sidebar over one shared `moduleIndex`; sidebar rows show each path minus its group's shared prefix | Approved by UD. Progressive reveal makes targeted retrieval mandatory — a thinned sky must never hide a module from someone who knows its name — and both surfaces reach every module whether charted or not. Basenames alone are useless in a Python project where every package carries an `__init__.py`, so rows keep enough real path to be told apart |
540
+ | 2026-07-21 | Progressive reveal stays **galaxy-only**; the Map always draws every module | Approved by UD when the navigation work was extended to the Map. The Map's job is "how it all fits together", and a layered import diagram with holes in it teaches less than a complete one; the galaxy already offers the thinned view for learners who want it |
541
+ | 2026-07-21 | Architecture boxes are named by the tail of their file path (`short_label`, map schema 3); `label` keeps the full identifier for title and aria | A box is a fixed width, so its text always truncates on a real project — and truncating a dotted region id rendered `codemble.server.app` and `codemble.server.runtime` as the same glyphs. Identical text for different modules is worse than no label, and it is exactly the kind of wrong a learner cannot detect. The path tail also survives the `__init__.py` collision a basename alone cannot |
542
+ | 2026-07-21 | The Map gains zoom, Fit, and drag-to-pan; panning rides the container's own scroll and zoom only scales the rendered size | The 2D counterpart of bounded orbit: a 960x2640 diagram in a plain scroll box showed four of nine layers and no way to see the whole shape. Scroll-based panning keeps native scrollbars, keyboard scrolling and screen-reader behaviour intact, and because every coordinate inside the SVG stays backend-computed, React remains a pure renderer of graph-owned geometry. It opens at true size rather than auto-fitting: fitting on mount measured the scroller before layout settled and landed on a scale that was neither fitted nor honest |
543
+ | 2026-07-21 | v0.6.0 deepens five private boundaries without changing the HTTP, graph, check, persistence, or learner-visible contracts: Project Selection, Project Activation, Project Mapping Run, Name Atlas, and Learner Projection | Approved by UD as five behavior-preserving waves in one release PR. The deletion test now holds at each seam, stale activation and mapping responses lose atomically, and dependency-scoped learner projections measured ~0.331 ms → ~0.001 ms per hover commit on a synthetic 1,000-node project while preserving derived outputs |
544
+ | 2026-07-21 | v0.6.1 treats Modules and Find as global surfaces, sequences first-run decisions as audience → required Home → coach, and makes the 3D parser-owned layout explicitly non-draggable | Approved by UD as implementation of every verified user-flow audit finding. Global commands must never accept hidden state, onboarding must expose one foreground decision at a time, and learners orbit the immutable graph rather than editing its coordinates. The compact shell is a structural breakpoint of the existing Formal Edo interface, not a new visual system |
465
545
 
466
546
  ## Non-Goals — do NOT build (point here when asked)
467
547
 
468
- - ❌ Free-flight 3D navigation — semantic zoom only
548
+ - ❌ ~~Free-flight 3D navigation~~superseded 2026-07-21: **bounded orbit** is
549
+ approved (see Decision Log). The camera may rotate and zoom around the current
550
+ subject; panning, free translation, and any control that can leave the subject
551
+ off screen remain out
469
552
  - ❌ XP, streaks, levels, leaderboards
470
553
  - ❌ ~~A second 2D renderer/toggle in v1~~ — superseded 2026-07-19: the 2D Map layer is approved (see Decision Log); free-form/client-computed 2D layouts remain out
471
554
  - ❌ Accounts, cloud hosting, multi-user; share link waits for Phase 3
@@ -0,0 +1,28 @@
1
+ # Codemble domain model
2
+
3
+ ## Project Selection
4
+
5
+ The learner's choice of one local folder, constrained to Codemble's allowed
6
+ browse root, that may become the current project.
7
+
8
+ ## Project Activation
9
+
10
+ The atomic transition from a successfully parsed project to the live checks,
11
+ study, graph, and map experience. Releasing a project cancels any unfinished
12
+ activation and leaves no project bound.
13
+
14
+ ## Project Mapping Run
15
+
16
+ The learner-visible run that begins when a selected folder is accepted and
17
+ ends when its project activates, parsing fails, or the learner releases it.
18
+
19
+ ## Name Atlas
20
+
21
+ The deterministic set of visible names placed around charted stars for the
22
+ current camera view, prioritizing Home, understood systems, and the learner's
23
+ current pointer subject without allowing name plates to collide.
24
+
25
+ ## Learner Projection
26
+
27
+ The learner-facing view of the current project after language focus, proven
28
+ progress, navigation selection, and view preferences are applied.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codemble
3
- Version: 0.5.3
3
+ Version: 0.6.1
4
4
  Summary: A learning game that turns the code AI wrote for you into a galaxy you light up by understanding it.
5
5
  Project-URL: Homepage, https://udhawan97.github.io/Codemble/
6
6
  Project-URL: Repository, https://github.com/udhawan97/Codemble
@@ -322,6 +322,13 @@ The [public roadmap](https://udhawan97.github.io/Codemble/roadmap/) separates
322
322
  shipped work from planned work. Milestones move only when their acceptance
323
323
  evidence exists.
324
324
 
325
+ ## Acknowledgements
326
+
327
+ - [dagre](https://github.com/dagrejs/dagre) and [Eclipse ELK](https://github.com/eclipse-elk/elk) for the layered-diagram approach.
328
+ - [tt-a1i/archify](https://github.com/tt-a1i/archify) for 2D architecture-diagram inspiration.
329
+ - [Graphify](https://github.com/Graphify-Labs/graphify) for the community-constellation idea.
330
+ - Codemble's shipped open-source stack: [3d-force-graph](https://github.com/vasturiano/3d-force-graph), [tree-sitter](https://github.com/tree-sitter/tree-sitter), [FastAPI](https://github.com/fastapi/fastapi), [Vite](https://github.com/vitejs/vite), and [React](https://github.com/facebook/react).
331
+
325
332
  ## License
326
333
 
327
334
  Codemble is released under the [Apache License 2.0](https://github.com/udhawan97/Codemble/blob/main/LICENSE).
@@ -290,6 +290,13 @@ The [public roadmap](https://udhawan97.github.io/Codemble/roadmap/) separates
290
290
  shipped work from planned work. Milestones move only when their acceptance
291
291
  evidence exists.
292
292
 
293
+ ## Acknowledgements
294
+
295
+ - [dagre](https://github.com/dagrejs/dagre) and [Eclipse ELK](https://github.com/eclipse-elk/elk) for the layered-diagram approach.
296
+ - [tt-a1i/archify](https://github.com/tt-a1i/archify) for 2D architecture-diagram inspiration.
297
+ - [Graphify](https://github.com/Graphify-Labs/graphify) for the community-constellation idea.
298
+ - Codemble's shipped open-source stack: [3d-force-graph](https://github.com/vasturiano/3d-force-graph), [tree-sitter](https://github.com/tree-sitter/tree-sitter), [FastAPI](https://github.com/fastapi/fastapi), [Vite](https://github.com/vitejs/vite), and [React](https://github.com/facebook/react).
299
+
293
300
  ## License
294
301
 
295
302
  Codemble is released under the [Apache License 2.0](https://github.com/udhawan97/Codemble/blob/main/LICENSE).
@@ -1,4 +1,4 @@
1
1
  """Codemble — a learning game that turns the code AI wrote for you into a galaxy
2
2
  you light up by understanding it. See CLAUDE.md for the agent brief and roadmap."""
3
3
 
4
- __version__ = "0.5.3"
4
+ __version__ = "0.6.1"
@@ -89,6 +89,13 @@ class Region:
89
89
  x: float
90
90
  y: float
91
91
  z: float
92
+ community: int
93
+ # Undirected breadth-first distance from the Home region over proven import
94
+ # routes. ``None`` means no route reaches this region -- or that no Home is
95
+ # selected yet -- and is never softened into a large number, because the
96
+ # renderer reveals the sky by this distance and a guessed one would quietly
97
+ # promise a path the parser never found.
98
+ hops_from_home: int | None = None
92
99
 
93
100
 
94
101
  @dataclass(frozen=True, slots=True)
@@ -115,7 +122,7 @@ class Graph:
115
122
  regions: tuple[Region, ...] = ()
116
123
  region_edges: tuple[RegionEdge, ...] = ()
117
124
  partial_files: tuple[str, ...] = ()
118
- schema_version: int = field(default=4, init=False)
125
+ schema_version: int = field(default=6, init=False)
119
126
 
120
127
  def to_dict(self) -> dict[str, object]:
121
128
  """Return a JSON-ready representation in canonical collection order."""
@@ -24,22 +24,72 @@ def layout_graph(graph: Graph) -> Graph:
24
24
  for node in graph.nodes:
25
25
  grouped[node.region].append(node)
26
26
 
27
+ node_by_id = {node.id: node for node in graph.nodes}
28
+
29
+ routes: dict[tuple[str, str], list[bool]] = defaultdict(list)
30
+ for edge in graph.edges:
31
+ if edge.kind != "import" or edge.external:
32
+ continue
33
+ src_node = node_by_id.get(edge.src)
34
+ dst_node = node_by_id.get(edge.dst)
35
+ if src_node is None or dst_node is None or src_node.region == dst_node.region:
36
+ continue
37
+ routes[(src_node.region, dst_node.region)].append(edge.certain)
38
+
39
+ # ``all``, deliberately, where ``mapview.py``'s ``_workflow`` uses ``any``
40
+ # for a call pair. The two marks claim different things. A workflow row
41
+ # asserts only that a relationship exists, so one proven call site settles
42
+ # it and the ambiguous ones beside it change nothing. A route is a single
43
+ # line standing in for ``weight`` imports, so calling it certain asserts
44
+ # every one of them: one unproven import among them and the whole route
45
+ # drops to possible. Under-claiming is the only direction that is safe
46
+ # when one mark speaks for many edges.
47
+ region_edges = tuple(
48
+ RegionEdge(src=src, dst=dst, weight=len(certainties), certain=all(certainties))
49
+ for (src, dst), certainties in sorted(routes.items())
50
+ )
51
+
27
52
  region_order = sorted(grouped, key=lambda region_id: (_digest(region_id), region_id))
53
+ communities = _communities(tuple(grouped), region_edges)
54
+ community_members: dict[int, list[str]] = defaultdict(list)
55
+ for region_id in region_order:
56
+ community_members[communities[region_id]].append(region_id)
57
+
58
+ region_positions: dict[str, tuple[float, float, float]] = {}
59
+ cumulative_members = 0
60
+ for community in sorted(community_members):
61
+ members = community_members[community]
62
+ community_angle = community * _GOLDEN_ANGLE
63
+ community_radius = 42.0 + 54.0 * math.sqrt(cumulative_members)
64
+ community_x = math.cos(community_angle) * community_radius
65
+ community_z = math.sin(community_angle) * community_radius
66
+ for member_index, region_id in enumerate(members):
67
+ local_angle = (
68
+ member_index * _GOLDEN_ANGLE
69
+ + _fraction(region_id, "phase") * 0.18
70
+ )
71
+ local_radius = 16.0 + 12.0 * math.sqrt(member_index)
72
+ region_positions[region_id] = (
73
+ _rounded(community_x + math.cos(local_angle) * local_radius),
74
+ _rounded(((_fraction(region_id, "height") * 2.0) - 1.0) * 28.0),
75
+ _rounded(community_z + math.sin(local_angle) * local_radius),
76
+ )
77
+ cumulative_members += len(members)
78
+
79
+ home_region = next(
80
+ (node.region for node in graph.nodes if node.id == graph.selected_entrypoint),
81
+ None,
82
+ )
83
+ hops = _hops_from_home(tuple(grouped), region_edges, home_region)
84
+
28
85
  regions: list[Region] = []
29
86
  positioned_nodes: list[Node] = []
30
- node_by_id = {node.id: node for node in graph.nodes}
31
-
32
- for region_index, region_id in enumerate(region_order):
87
+ for region_id in region_order:
33
88
  members = sorted(
34
89
  grouped[region_id],
35
90
  key=lambda node: (node.kind != "module", node.id),
36
91
  )
37
- region_angle = region_index * _GOLDEN_ANGLE + _fraction(region_id, "phase") * 0.18
38
- region_radius = 42.0 + 54.0 * math.sqrt(region_index)
39
- region_x = _rounded(math.cos(region_angle) * region_radius)
40
- region_y = _rounded(((_fraction(region_id, "height") * 2.0) - 1.0) * 28.0)
41
- region_z = _rounded(math.sin(region_angle) * region_radius)
42
-
92
+ region_x, region_y, region_z = region_positions[region_id]
43
93
  module_nodes = [node for node in members if node.kind == "module"]
44
94
  loc = sum(node.loc for node in module_nodes) or sum(node.loc for node in members)
45
95
  regions.append(
@@ -54,6 +104,8 @@ def layout_graph(graph: Graph) -> Graph:
54
104
  x=region_x,
55
105
  y=region_y,
56
106
  z=region_z,
107
+ community=communities[region_id],
108
+ hops_from_home=hops.get(region_id),
57
109
  )
58
110
  )
59
111
 
@@ -85,28 +137,6 @@ def layout_graph(graph: Graph) -> Graph:
85
137
  )
86
138
  )
87
139
 
88
- routes: dict[tuple[str, str], list[bool]] = defaultdict(list)
89
- for edge in graph.edges:
90
- if edge.kind != "import" or edge.external:
91
- continue
92
- src_node = node_by_id.get(edge.src)
93
- dst_node = node_by_id.get(edge.dst)
94
- if src_node is None or dst_node is None or src_node.region == dst_node.region:
95
- continue
96
- routes[(src_node.region, dst_node.region)].append(edge.certain)
97
-
98
- # ``all``, deliberately, where ``mapview.py``'s ``_workflow`` uses ``any``
99
- # for a call pair. The two marks claim different things. A workflow row
100
- # asserts only that a relationship exists, so one proven call site settles
101
- # it and the ambiguous ones beside it change nothing. A route is a single
102
- # line standing in for ``weight`` imports, so calling it certain asserts
103
- # every one of them: one unproven import among them and the whole route
104
- # drops to possible. Under-claiming is the only direction that is safe
105
- # when one mark speaks for many edges.
106
- region_edges = tuple(
107
- RegionEdge(src=src, dst=dst, weight=len(certainties), certain=all(certainties))
108
- for (src, dst), certainties in sorted(routes.items())
109
- )
110
140
  return replace(
111
141
  graph,
112
142
  nodes=tuple(sorted(positioned_nodes, key=lambda node: node.id)),
@@ -115,6 +145,90 @@ def layout_graph(graph: Graph) -> Graph:
115
145
  )
116
146
 
117
147
 
148
+ def _communities(
149
+ region_ids: tuple[str, ...], routes: tuple[RegionEdge, ...]
150
+ ) -> dict[str, int]:
151
+ """Find deterministic import communities with label propagation.
152
+
153
+ Label propagation after Raghavan, Albert & Kumara (2007), "Near linear
154
+ time algorithm to detect community structures in large-scale networks";
155
+ constellation idea inspired by graphify. Implemented independently; no
156
+ code copied.
157
+ """
158
+
159
+ ordered = sorted(region_ids)
160
+ neighbors: dict[str, set[str]] = {region_id: set() for region_id in ordered}
161
+ for route in sorted(routes, key=lambda edge: (edge.src, edge.dst)):
162
+ if route.src not in neighbors or route.dst not in neighbors or route.src == route.dst:
163
+ continue
164
+ neighbors[route.src].add(route.dst)
165
+ neighbors[route.dst].add(route.src)
166
+
167
+ labels = {region_id: index for index, region_id in enumerate(ordered)}
168
+ for _ in range(10):
169
+ next_labels = dict(labels)
170
+ for region_id in ordered:
171
+ if not neighbors[region_id]:
172
+ continue
173
+ frequencies: dict[int, int] = defaultdict(int)
174
+ for neighbor in sorted(neighbors[region_id]):
175
+ frequencies[labels[neighbor]] += 1
176
+ highest = max(frequencies.values())
177
+ next_labels[region_id] = min(
178
+ label for label, count in frequencies.items() if count == highest
179
+ )
180
+ if next_labels == labels:
181
+ break
182
+ labels = next_labels
183
+
184
+ dense_labels: dict[int, int] = {}
185
+ communities: dict[str, int] = {}
186
+ for region_id in ordered:
187
+ label = labels[region_id]
188
+ if label not in dense_labels:
189
+ dense_labels[label] = len(dense_labels)
190
+ communities[region_id] = dense_labels[label]
191
+ return communities
192
+
193
+
194
+ def _hops_from_home(
195
+ region_ids: tuple[str, ...],
196
+ routes: tuple[RegionEdge, ...],
197
+ home: str | None,
198
+ ) -> dict[str, int]:
199
+ """Return each region's import-route distance from Home.
200
+
201
+ Undirected on purpose: an import route is a relationship between two
202
+ modules, and a learner following it from Home arrives just as surely at the
203
+ importer as at the imported. Direction is still parser truth and is still
204
+ drawn as an arrow -- it just is not what "how far from Home" means.
205
+
206
+ Regions absent from the result have no route to Home at all. The caller
207
+ stores that as ``None`` rather than a sentinel distance, so "unreachable"
208
+ can never be mistaken for "very far".
209
+ """
210
+
211
+ if home is None or home not in set(region_ids):
212
+ return {}
213
+
214
+ neighbors: dict[str, set[str]] = {region_id: set() for region_id in region_ids}
215
+ for route in routes:
216
+ if route.src not in neighbors or route.dst not in neighbors:
217
+ continue
218
+ neighbors[route.src].add(route.dst)
219
+ neighbors[route.dst].add(route.src)
220
+
221
+ hops = {home: 0}
222
+ queue: deque[str] = deque([home])
223
+ while queue:
224
+ current = queue.popleft()
225
+ for neighbor in sorted(neighbors[current]):
226
+ if neighbor not in hops:
227
+ hops[neighbor] = hops[current] + 1
228
+ queue.append(neighbor)
229
+ return hops
230
+
231
+
118
232
  def _call_depths(members: list[Node], edges: tuple[Edge, ...]) -> dict[str, int]:
119
233
  """Return each member's orbit ring: call depth from the system's entry node.
120
234
 
@@ -169,8 +283,20 @@ def with_entrypoint(graph: Graph, node_id: str) -> Graph:
169
283
  raise ValueError(f"entrypoint is not a parser-ranked candidate: {node_id}")
170
284
  node_by_id = {node.id: node for node in graph.nodes}
171
285
  selected = node_by_id[node_id]
286
+ # Every distance is measured from Home, so moving Home invalidates all of
287
+ # them. Coordinates and communities are untouched: they depend on the import
288
+ # structure, not on which entrypoint the learner chose, so a Home change
289
+ # must not re-sort the sky or re-dim a region.
290
+ hops = _hops_from_home(
291
+ tuple(region.id for region in graph.regions), graph.region_edges, selected.region
292
+ )
172
293
  regions = tuple(
173
- replace(region, home=region.id == selected.region) for region in graph.regions
294
+ replace(
295
+ region,
296
+ home=region.id == selected.region,
297
+ hops_from_home=hops.get(region.id),
298
+ )
299
+ for region in graph.regions
174
300
  )
175
301
  return replace(graph, selected_entrypoint=node_id, regions=regions)
176
302