codemble 0.6.2__tar.gz → 0.6.4__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.
- {codemble-0.6.2 → codemble-0.6.4}/CHANGELOG.md +73 -0
- {codemble-0.6.2 → codemble-0.6.4}/CLAUDE.md +40 -12
- {codemble-0.6.2 → codemble-0.6.4}/PKG-INFO +8 -3
- {codemble-0.6.2 → codemble-0.6.4}/README.md +7 -2
- {codemble-0.6.2 → codemble-0.6.4}/TESTING.md +7 -1
- {codemble-0.6.2 → codemble-0.6.4}/codemble/__init__.py +1 -1
- {codemble-0.6.2 → codemble-0.6.4}/codemble/checks/service.py +14 -8
- {codemble-0.6.2 → codemble-0.6.4}/codemble/lens/python.py +6 -1
- {codemble-0.6.2 → codemble-0.6.4}/codemble/progress/store.py +44 -7
- {codemble-0.6.2 → codemble-0.6.4}/codemble/server/project_selection.py +16 -1
- codemble-0.6.4/codemble/web_dist/assets/index-BBC-ag63.css +1 -0
- codemble-0.6.2/codemble/web_dist/assets/index-_4l8WmkY.js → codemble-0.6.4/codemble/web_dist/assets/index-DvJD5a9M.js +1 -1
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/index.html +2 -2
- codemble-0.6.4/docs/releases/v0.6.3.md +40 -0
- codemble-0.6.4/docs/releases/v0.6.4.md +89 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/package.json +1 -1
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/quickstart.md +5 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/study-panel.md +5 -1
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/the-galaxy.md +12 -3
- {codemble-0.6.2 → codemble-0.6.4}/pyproject.toml +1 -1
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_checks.py +29 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_progress.py +55 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_project_selection.py +34 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/package-lock.json +2 -2
- {codemble-0.6.2 → codemble-0.6.4}/web/package.json +2 -2
- codemble-0.6.4/web/scripts/check_focus_handoffs.mjs +22 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_graph_data.mjs +32 -2
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_learner_projection.mjs +125 -1
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_learner_session.mjs +25 -2
- codemble-0.6.4/web/scripts/check_map_viewport.mjs +38 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/App.jsx +392 -98
- {codemble-0.6.2 → codemble-0.6.4}/web/src/GuidanceLayer.jsx +7 -5
- {codemble-0.6.2 → codemble-0.6.4}/web/src/MapView.jsx +97 -50
- {codemble-0.6.2 → codemble-0.6.4}/web/src/ModeControl.jsx +1 -20
- {codemble-0.6.2 → codemble-0.6.4}/web/src/StudyPanel.jsx +8 -3
- {codemble-0.6.2 → codemble-0.6.4}/web/src/graphData.js +24 -6
- {codemble-0.6.2 → codemble-0.6.4}/web/src/learnerProjection.js +74 -10
- {codemble-0.6.2 → codemble-0.6.4}/web/src/learnerSession.js +31 -0
- codemble-0.6.4/web/src/mapViewport.js +45 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/styles.css +121 -28
- codemble-0.6.2/codemble/web_dist/assets/index-CsUbeE2C.css +0 -1
- {codemble-0.6.2 → codemble-0.6.4}/.env.example +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.gitattributes +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/ISSUE_TEMPLATE/early_tester.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/dependabot.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/workflows/ci.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/workflows/pages.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.github/workflows/publish-pypi.yml +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/.gitignore +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/AGENTS.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/CODE_OF_CONDUCT.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/CONTEXT.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/CONTRIBUTING.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/LICENSE +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/NOTICE +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/SECURITY.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/assets/demo.gif +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/base.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/discovery.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/parse_progress.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/project.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/python_ast.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/adapters/typescript_tree_sitter.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/checks/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/cli.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/graph/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/graph/finalize.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/graph/layout.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/graph/mapview.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/lens/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/lens/javascript_typescript.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/llm/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/llm/local_status.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/llm/providers.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/llm/structural.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/llm/study.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/paths.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/progress/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/server/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/server/app.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/server/parse_job.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/server/project_activation.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/server/runtime.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2 +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2 +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2 +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/adr/README.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/2026-07-19-audience-modes-and-local-narration-design.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/2026-07-19-audience-modes-backend-plan.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/2026-07-19-audience-modes-frontend-plan.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/2026-07-19-install-ux-folder-picker-design.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/2026-07-19-install-ux-folder-picker-plan.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/2026-07-20-oss-inspired-map-and-constellations.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/README.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/plans/phase-1.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/checklist.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.1.0.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.2.0.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.4.0.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.5.0.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.5.1.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.5.2.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.5.3.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.6.1.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/releases/v0.6.2.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/research/2026-07-20-opensource-graph-architecture-inspiration.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/research/README.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-a.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-b.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-c.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/superpowers/plans/2026-07-19-galaxy-ux-shared-contract.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/superpowers/specs/2026-07-19-galaxy-ux-overhaul-design.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs/superpowers/specs/2026-07-21-navigation-design-clarity-design.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/.gitignore +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/astro.config.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/design.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/favicon-16.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/favicon-32.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/icon-180.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/icon-192.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/icon-512.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/icon.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/icons/asterism.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/icons/install.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/mark-animated.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/mark.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/hero-chart.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/hero-field.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/hero-gold.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/kasumi-rule.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/plate-galaxy.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/plate-study.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/plate-system.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/brand/plates/seal.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/favicon.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/easy-mode.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/galaxy-lit.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/galaxy.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/loading.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/map-architecture.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/map-workflow.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/study-panel.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/public/shots/system.png +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/scripts/build-plates.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/assets/codemble-mark-dark.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/assets/codemble-mark-light.svg +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/components/Search.astro +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/components/Tatebanko.astro +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/architecture.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/build-from-source.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/checks-and-lighting.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/contributing.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/correctness.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/early-testing.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/installation.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/introduction.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m1-parser.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m10-polyglot-release.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m12-galaxy-look.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m13-scale.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m2-galaxy.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m3-study.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m4-lens.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m5-checks.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m6-release.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m8-typescript.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/progress/m9-typescript-lens.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/roadmap.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content/docs/star-chart.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/content.config.ts +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/pages/index.astro +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/styles/custom.css +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/styles/landing.css +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/docs-site/src/styles/tokens.css +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/scripts/dev.sh +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/scripts/record_demo.sh +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/conftest.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/check_suites.json +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/concepts_sample.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/impact/alpha.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/impact/beta.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/impact/gamma.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/impact/helpers.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/impact/noisy.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/python_worker.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/broken.ts +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/legacy.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/local.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/main.ts +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/reexport.ts +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/util.ts +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/polyglot/src/widget.tsx +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/repeated_calls.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/.gitignore +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/ambiguous.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/api.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/app.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/broken.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/cli.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/pkg/__init__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/pkg/helpers.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/pkg/service.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/pkg/util.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/runner/__main__.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/fixtures/sampleproj/shared.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_dependency_compatibility.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_graph_finalization.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_mapview.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_parse_job.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_parse_progress.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_project_activation.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_project_parser.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_providers.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_python_ast.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_server.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_smoke.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_structural.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_study.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/tests/test_typescript_tree_sitter.py +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/README.md +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/index.html +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/benchmark_learner_projection.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_mode_gate_portal.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_name_atlas.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_orbit_pointer_guard.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/check_project_mapping.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/scripts/record_demo.mjs +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/GalaxyCanvas.jsx +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/galaxyEffects.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/galaxyMaterials.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/main.jsx +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/nameAtlas.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/orbitPointerGuard.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/projectMapping.js +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/src/tokens.css +0 -0
- {codemble-0.6.2 → codemble-0.6.4}/web/vite.config.js +0 -0
|
@@ -5,6 +5,79 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.6.4] - 2026-07-21
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **A missed check no longer hands back its own answer.** Getting a question
|
|
12
|
+
wrong printed the parser answer and its evidence, and the same question then
|
|
13
|
+
accepted that answer — so a region could light without understanding. A miss
|
|
14
|
+
now returns neither the answer nor the citations (an importer check's
|
|
15
|
+
evidence *is* its answer); both appear once the learner has proven it.
|
|
16
|
+
- **The Map can show you the code it is asking about.** Easy mode lands on the
|
|
17
|
+
Map, which offered checks but no way to read a module. Region focus now
|
|
18
|
+
carries **Read the source** beside **Prove understanding**, and Easy guidance
|
|
19
|
+
recommends reading before proving instead of sending the learner to another
|
|
20
|
+
layer. Escape steps back a level on the Map, as it always has in the Galaxy.
|
|
21
|
+
- **The checks panel is usable from the keyboard.** Opening it hands focus to
|
|
22
|
+
the panel instead of leaving it on the trigger behind ~114 tab stops, and
|
|
23
|
+
submitting keeps focus on the result rather than dropping it to the document.
|
|
24
|
+
- **Enter opens the structure the arrow keys selected.** At study level the
|
|
25
|
+
canvas announced a sibling while the panel kept showing the old one; Enter
|
|
26
|
+
was a no-op despite the view's own instructions.
|
|
27
|
+
- **Guidance is docked, and waits its turn.** The hint chip took its own strip
|
|
28
|
+
above the footer instead of floating over captions, name plates and its own
|
|
29
|
+
button, and it no longer appears during the audience, Home and coach steps —
|
|
30
|
+
where it used to recommend a target "because no import route reaches it from
|
|
31
|
+
Home" while no Home existed.
|
|
32
|
+
- **Compact Map controls no longer stack.** At 375 px the Key button covered
|
|
33
|
+
40% of the "What runs first" tab and the zoom pill covered the top row of
|
|
34
|
+
boxes. Measured at 320 and 375: zero overlapping interactive rectangles.
|
|
35
|
+
- **The study panel stops contradicting itself.** Easy mode showed "Used by 0"
|
|
36
|
+
above a summary saying five other parts use the file; the call-edge stat is
|
|
37
|
+
now "Called by", leaving "use" to imports.
|
|
38
|
+
- **The audience question is asked once per learner, not once per project.**
|
|
39
|
+
A new project inherits the last answer; the header toggle still overrides any
|
|
40
|
+
single project.
|
|
41
|
+
- **Home calibration is navigable on real projects.** It is now a modal sized
|
|
42
|
+
to the window rather than a card capped to a short stage, states how many
|
|
43
|
+
candidates exist, groups them by their real top-level scope (so test fixtures
|
|
44
|
+
no longer sit unmarked beside your entrypoint), and keeps "Explore without
|
|
45
|
+
Home" on screen instead of thousands of pixels below the fold.
|
|
46
|
+
- **The star chart is reachable from anywhere and closes with Escape.** It kept
|
|
47
|
+
its header slot at every level instead of surrendering it to the level-return
|
|
48
|
+
button, and every exit now names the layer it returns to.
|
|
49
|
+
- **Galaxy plates are named by path tail**, the rule the Map's boxes already
|
|
50
|
+
use, so two different `__init__.py` modules no longer wear identical names.
|
|
51
|
+
- **Find opens on Home and the most-used modules** instead of a screen of
|
|
52
|
+
identical `__init__.py` rows.
|
|
53
|
+
- **Easy guidance prefers a substantive module** when several sit the same
|
|
54
|
+
number of import hops from Home, instead of the alphabetically-first
|
|
55
|
+
four-line package init.
|
|
56
|
+
- **The exception-handling lens note is true of `raise`,** not only of
|
|
57
|
+
`try`/`except`, since the parser files all three under one concept.
|
|
58
|
+
- **Counts of one read as one** ("1 structure", not "1 structures").
|
|
59
|
+
- **The picker opens beside your last project** rather than at your home
|
|
60
|
+
directory every time.
|
|
61
|
+
|
|
62
|
+
## [0.6.3] - 2026-07-21
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
- **Easy guidance always advances or becomes an honest instruction.** Opening
|
|
66
|
+
the suggested system from the Map now changes the next action to **View
|
|
67
|
+
structures**, which switches to the Galaxy. Once those structures are on
|
|
68
|
+
screen, the chip asks the learner to choose one and removes the button instead
|
|
69
|
+
of offering an enabled action that leaves the session unchanged.
|
|
70
|
+
- **The compact Map opens readable and stays where the learner put it.** A phone
|
|
71
|
+
now starts at 100% around Home or the selected parser-backed target; **Fit**
|
|
72
|
+
remains the explicit whole-diagram overview. Zoom and scroll survive Map data
|
|
73
|
+
refreshes and layer remounts, while a project lifecycle reset clears them.
|
|
74
|
+
- **Switch project confirms on the first compact-Menu click.** The trigger and
|
|
75
|
+
its confirmation now share one disclosure boundary, and Menu state is cleared
|
|
76
|
+
when the project leaves ready state or the rail crosses to its desktop layout.
|
|
77
|
+
- **Keyboard focus follows every global surface.** Home calibration, the coach,
|
|
78
|
+
Modules, Find, and the Star chart receive focus on entry and return it to the
|
|
79
|
+
invoking control—or the visible compact Menu fallback—when they close.
|
|
80
|
+
|
|
8
81
|
## [0.6.2] - 2026-07-21
|
|
9
82
|
|
|
10
83
|
### Fixed
|
|
@@ -168,18 +168,38 @@ Polish, then the coordinated launch (Show HN / X; lit-galaxy GIF as hero).
|
|
|
168
168
|
## Current State **[AGENT-MAINTAINED]**
|
|
169
169
|
|
|
170
170
|
**Current milestone: Phase 1 tester evidence** · Last updated: 2026-07-21 ·
|
|
171
|
-
Session note: v0.6.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
171
|
+
Session note: v0.6.4 closes all twenty findings of a fresh end-to-end user-flow
|
|
172
|
+
audit run against the served build on three real projects (Codemble, Golavo,
|
|
173
|
+
FolioOrb) at 1280/375/320 px with a keyboard pass. The headline fix is a
|
|
174
|
+
Correctness Contract one: a missed check printed the parser answer and its
|
|
175
|
+
evidence and then accepted that answer, so a region could light without
|
|
176
|
+
understanding; a miss now returns neither, and both appear only after the
|
|
177
|
+
learner proves it. The Easy default layer gained the reading path it never had
|
|
178
|
+
(**Read the source** on the Map, guidance that says read-before-prove, Escape
|
|
179
|
+
stepping back a level), the checks panel became keyboard-usable (focus handoff
|
|
180
|
+
on open, focus preserved across submits), Enter now opens the arrow-selected
|
|
181
|
+
structure at study level, and the guidance chip is docked into its own strip
|
|
182
|
+
and hidden until the first-run decisions finish. Home calibration is now a
|
|
183
|
+
viewport-sized modal that states its candidate count, groups candidates by
|
|
184
|
+
their real scope, and keeps its escape hatch on screen; the audience question
|
|
185
|
+
is asked once per learner instead of once per project; the star chart is
|
|
186
|
+
reachable from every level and closes with Escape; exits name the layer they
|
|
187
|
+
return to; galaxy plates and the module index use path tails; Find opens on
|
|
188
|
+
Home and the busiest modules; and compact Map controls no longer share touch
|
|
189
|
+
targets (zero overlapping interactive rectangles at 320 and 375). Parser,
|
|
190
|
+
graph, checks, progress, and provider contracts are unchanged except for the
|
|
191
|
+
deliberate withholding of answers on a failed submission. Previously, v0.6.3
|
|
192
|
+
closed the four follow-up findings from the earlier user-flow audit. Easy guidance is now level-aware and never offers an enabled
|
|
193
|
+
no-op; compact Maps open at readable 100% around Home and preserve zoom/pan
|
|
194
|
+
through data refreshes; Switch project confirms on the first compact-Menu click
|
|
195
|
+
without leaking disclosure state across project or breakpoint changes; and Home
|
|
196
|
+
calibration, the coach, Modules, Find, and the Star chart own explicit keyboard
|
|
197
|
+
focus handoffs. Parser, graph, checks, progress, and provider contracts remain
|
|
198
|
+
unchanged. The bundled app is verified across compact and desktop widths; the
|
|
199
|
+
milestone does not advance because issue #13 still requires human tester
|
|
200
|
+
evidence. v0.6.2 was the immediate installed-artifact fix that moved the
|
|
201
|
+
first-run audience modal to the document top layer, and v0.6.1 remains the
|
|
202
|
+
responsive learning-loop release. The v0.6.0
|
|
183
203
|
architecture-depth pass is complete in five
|
|
184
204
|
behavior-preserving waves: project selection owns the home-jailed filesystem
|
|
185
205
|
policy; project activation atomically owns parse-to-live binding and graph/map
|
|
@@ -548,6 +568,14 @@ shows lower repeated-commit work without changing derived values.
|
|
|
548
568
|
| 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 |
|
|
549
569
|
| 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 |
|
|
550
570
|
| 2026-07-21 | First-run audience modal portals to `document.body`; the persistent Easy/Expert toggle remains in responsive header chrome | A native modal inside the closed compact Menu entered the top layer but inherited `display:none` from its ancestor, leaving an invisible backdrop that blocked fresh mobile runs. Modal ownership is a document boundary, not header layout. Caught only by the clean public v0.6.1 installed-artifact smoke; PyPI immutability requires v0.6.2 rather than replacing 0.6.1 |
|
|
571
|
+
| 2026-07-21 | Easy guidance actions are derived from level, region, and layer, then executed by `LearnerSession`; the chip renders no button when the next step is already on screen | React must not guess a structure or own navigation truth, and an enabled action that commits the same state is a false promise. The nearest unlit region remains graph-derived; only the honest route to it changes with the learner's current context |
|
|
572
|
+
| 2026-07-21 | Map zoom/pan is renderer-local state keyed by tab and Home, preserved through transient data remounts but cleared with the project lifecycle; compact Maps start at 100% centred on the parser-backed target | Auto-fitting made 56 px boxes as little as 8–18 px tall and re-ran after check-driven map refreshes. Fit is still a valid explicit overview, while session state stays reserved for graph and learning truth |
|
|
573
|
+
| 2026-07-21 | Responsive disclosures and global surfaces own explicit focus handoffs; compact Menu closes on project exit and when crossing to the desktop rail | DOM focus and disclosure visibility are view concerns, but leaving focus on removed or hidden controls makes a successful navigation indistinguishable from a dead action to a keyboard or screen-reader user |
|
|
574
|
+
| 2026-07-21 | A wrong check submission returns no answer, no answer labels and no evidence; all three are returned only once the learner answers correctly | The response printed the parser answer on every miss and the same question then accepted it, so a region could light on an answer the app itself had just displayed — illumination stopped meaning understanding, the same failure class as the 2026-07-19 "every check needs a wrong option" fix. Evidence is withheld with the answer because an importer check cites exactly the files that *are* its answer |
|
|
575
|
+
| 2026-07-21 | The 2D Map gets a reading path: region focus offers **Read the source** beside the checks, Easy guidance recommends reading before proving, and Escape steps back a level there as it does in the Galaxy | Easy mode lands on the Map, where the only action was a quiz about code the layer could not show. The study panel is layer-neutral (`/api/node/:id/study`), so the Map only needed to select the module node the parser already produced — no new truth, and the audience that most needs to read first stops being sent to another layer to do it |
|
|
576
|
+
| 2026-07-21 | The audience answer is stored per learner as well as per project (`learner.json` beside progress); a fresh bind seeds from it and skips the gate, while the header toggle still overrides one project | The gate asks who the *learner* is, but the answer lived only under the project key, so every new project re-asked an expert whether they were new to coding. The file carries no `schema_version`, which is what keeps recents from reading it as a project |
|
|
577
|
+
| 2026-07-21 | Home calibration is a native modal sized to the viewport, grouped by the candidates' real top-level scope, with the candidate count stated and "Explore without Home" outside the scrolling list | It is the second step of the same required sequence as the audience gate and deserves the same shape. As a card capped to a share of the stage it showed one candidate of eleven with the escape hatch thousands of pixels below the fold, and a flat list put `tests/fixtures/...` beside the learner's entrypoint with nothing to tell them apart. Scope and rank are parser facts already in the payload; the leading group always opens so a project whose best candidate is rank 1 is never met by an all-collapsed list |
|
|
578
|
+
| 2026-07-21 | Galaxy name plates use the same path-tail rule as map schema 3's `short_label`; the shared module index and the command palette use it too, and the palette's unfiltered order is Home → lit → centrality | Basenames collide hard in a Python project — every package carries an `__init__.py` — so identical plates named different modules, which is precisely the wrong a learner cannot detect, and the palette opened on a screen of indistinguishable rows |
|
|
551
579
|
|
|
552
580
|
## Non-Goals — do NOT build (point here when asked)
|
|
553
581
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codemble
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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
|
|
@@ -188,6 +188,9 @@ computed in the graph layer and served as data.
|
|
|
188
188
|
| **Map · Workflow** | The call tree from your entrypoint, depth by depth | Seeing what runs first |
|
|
189
189
|
|
|
190
190
|
The Map is plain SVG, so it still works on a machine that cannot draw WebGL.
|
|
191
|
+
On a compact screen it opens at readable 100% around Home or the selected
|
|
192
|
+
target; **Fit** is an explicit whole-diagram overview. Zoom and pan survive Map
|
|
193
|
+
refreshes and layer switches instead of snapping back after a passed check.
|
|
191
194
|
|
|
192
195
|
## Open a structure, read what the parser knows first
|
|
193
196
|
|
|
@@ -204,8 +207,10 @@ Sections other than the narration never involve a model at all.
|
|
|
204
207
|
A header toggle changes how Codemble talks to you and how much it puts on
|
|
205
208
|
screen: plain language, larger type, the Map by default, and a hint chip naming
|
|
206
209
|
the nearest unlit region to Home — counted in import hops over the graph, not
|
|
207
|
-
chosen by a model.
|
|
208
|
-
|
|
210
|
+
chosen by a model. The hint opens that system, switches from Map to Galaxy when
|
|
211
|
+
its internal structures are the next step, then becomes an instruction rather
|
|
212
|
+
than an enabled no-op. It never changes graph truth, coordinates, progress, or
|
|
213
|
+
how a check is scored.
|
|
209
214
|
|
|
210
215
|
You can also switch project or change Home without leaving the app.
|
|
211
216
|
|
|
@@ -156,6 +156,9 @@ computed in the graph layer and served as data.
|
|
|
156
156
|
| **Map · Workflow** | The call tree from your entrypoint, depth by depth | Seeing what runs first |
|
|
157
157
|
|
|
158
158
|
The Map is plain SVG, so it still works on a machine that cannot draw WebGL.
|
|
159
|
+
On a compact screen it opens at readable 100% around Home or the selected
|
|
160
|
+
target; **Fit** is an explicit whole-diagram overview. Zoom and pan survive Map
|
|
161
|
+
refreshes and layer switches instead of snapping back after a passed check.
|
|
159
162
|
|
|
160
163
|
## Open a structure, read what the parser knows first
|
|
161
164
|
|
|
@@ -172,8 +175,10 @@ Sections other than the narration never involve a model at all.
|
|
|
172
175
|
A header toggle changes how Codemble talks to you and how much it puts on
|
|
173
176
|
screen: plain language, larger type, the Map by default, and a hint chip naming
|
|
174
177
|
the nearest unlit region to Home — counted in import hops over the graph, not
|
|
175
|
-
chosen by a model.
|
|
176
|
-
|
|
178
|
+
chosen by a model. The hint opens that system, switches from Map to Galaxy when
|
|
179
|
+
its internal structures are the next step, then becomes an instruction rather
|
|
180
|
+
than an enabled no-op. It never changes graph truth, coordinates, progress, or
|
|
181
|
+
how a check is scored.
|
|
177
182
|
|
|
178
183
|
You can also switch project or change Home without leaving the app.
|
|
179
184
|
|
|
@@ -35,10 +35,16 @@ Then, without reading the docs first:
|
|
|
35
35
|
1. Identify Home, or choose it if Codemble asks.
|
|
36
36
|
2. If the project is mixed, focus one language and return to **All**; report any
|
|
37
37
|
missing system or relationship.
|
|
38
|
-
3. Enter one system and open one source structure.
|
|
38
|
+
3. Enter one system and open one source structure. On a narrow window, also use
|
|
39
|
+
**Menu** to open and close **Modules** and **Find**; keyboard focus should
|
|
40
|
+
move into each surface and return to **Menu** when it closes.
|
|
39
41
|
4. Return to the system, choose **Prove understanding**, and light it.
|
|
40
42
|
5. Quit Codemble, run the same command again, and confirm the system stays lit.
|
|
41
43
|
|
|
44
|
+
On the Map, compact screens should start at readable 100% around Home. **Fit**
|
|
45
|
+
is the whole-diagram overview; after zooming or panning, a passed check should
|
|
46
|
+
not throw you to a different scale or location.
|
|
47
|
+
|
|
42
48
|
## Report what happened
|
|
43
49
|
|
|
44
50
|
Use the [early-tester issue form](https://github.com/udhawan97/Codemble/issues/new/choose).
|
|
@@ -124,22 +124,28 @@ class CheckService:
|
|
|
124
124
|
if complete:
|
|
125
125
|
self._progress.mark_understood(region_id)
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
option.label for option in check.options if option.id in check.answer_ids
|
|
129
|
-
]
|
|
130
|
-
return {
|
|
127
|
+
result: dict[str, object] = {
|
|
131
128
|
"correct": correct,
|
|
132
129
|
"check_id": check.id,
|
|
133
|
-
"answer_ids": list(check.answer_ids),
|
|
134
|
-
"answer_labels": answer_labels,
|
|
135
|
-
"evidence": list(check.evidence),
|
|
136
130
|
"message": (
|
|
137
131
|
"Correct. That answer is fixed by the parser graph."
|
|
138
132
|
if correct
|
|
139
|
-
else "Not yet. Re-read the
|
|
133
|
+
else "Not yet. Re-read the relationship in your own code and try again."
|
|
140
134
|
),
|
|
141
135
|
"region_understood": complete,
|
|
142
136
|
}
|
|
137
|
+
if not correct:
|
|
138
|
+
# A miss returns no answer and no citations. An importer check
|
|
139
|
+
# cites the very files that are its answer, so handing either back
|
|
140
|
+
# let the next submission replay what the screen had just shown --
|
|
141
|
+
# and a region lit that way proves nothing.
|
|
142
|
+
return result
|
|
143
|
+
result["answer_ids"] = list(check.answer_ids)
|
|
144
|
+
result["answer_labels"] = [
|
|
145
|
+
option.label for option in check.options if option.id in check.answer_ids
|
|
146
|
+
]
|
|
147
|
+
result["evidence"] = list(check.evidence)
|
|
148
|
+
return result
|
|
143
149
|
|
|
144
150
|
def _region_checks(self, region_id: str) -> tuple[Check, ...]:
|
|
145
151
|
if region_id not in self._checks:
|
|
@@ -47,10 +47,15 @@ _PYTHON_NOTES = {
|
|
|
47
47
|
"expert": "Python calls this specially named method through a language protocol such as length, comparison, or display.",
|
|
48
48
|
},
|
|
49
49
|
),
|
|
50
|
+
# One concept id covers `try`, `try*` and `raise` (python_ast visits all
|
|
51
|
+
# three), so the copy has to be true of a line that *signals* a problem as
|
|
52
|
+
# well as one that catches it. The old easy note taught catching only, and
|
|
53
|
+
# anchored to `raise SystemExit(main())` it claimed roughly the opposite of
|
|
54
|
+
# what that line does.
|
|
50
55
|
"exception-handling": (
|
|
51
56
|
"Exception handling",
|
|
52
57
|
{
|
|
53
|
-
"easy": "This
|
|
58
|
+
"easy": "This treats failure as part of the plan: code like this either raises a problem for whoever called it, or catches one so the program can react instead of crashing.",
|
|
54
59
|
"expert": "This construct makes failure part of explicit control flow by catching, grouping, or raising an exception.",
|
|
55
60
|
},
|
|
56
61
|
),
|
|
@@ -12,6 +12,11 @@ from codemble.adapters.base import Graph
|
|
|
12
12
|
from codemble.paths import data_dir
|
|
13
13
|
|
|
14
14
|
_SCHEMA_VERSION = 1
|
|
15
|
+
_MODES = frozenset({"easy", "expert"})
|
|
16
|
+
# The audience answer is about the learner, not the project, so it is also kept
|
|
17
|
+
# once per data directory. Project payloads still win, which is what keeps the
|
|
18
|
+
# header toggle a genuine per-project override.
|
|
19
|
+
_LEARNER_FILE = "learner.json"
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
class UnknownRegionError(KeyError):
|
|
@@ -26,6 +31,7 @@ class ProgressStore:
|
|
|
26
31
|
self._root = root or data_dir() / "progress"
|
|
27
32
|
project_key = hashlib.sha256(graph.project_root.encode()).hexdigest()[:20]
|
|
28
33
|
self.path = self._root / f"{project_key}.json"
|
|
34
|
+
self._learner_path = self._root / _LEARNER_FILE
|
|
29
35
|
self._signatures = _region_signatures(graph)
|
|
30
36
|
|
|
31
37
|
def understood_regions(self) -> frozenset[str]:
|
|
@@ -72,24 +78,55 @@ class ProgressStore:
|
|
|
72
78
|
def mode(self) -> str:
|
|
73
79
|
"""Return the learner's audience mode; this never affects progress."""
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
value
|
|
77
|
-
|
|
81
|
+
value = self._read().get("mode")
|
|
82
|
+
if value in _MODES:
|
|
83
|
+
return value
|
|
84
|
+
return self._learner_mode() or "easy"
|
|
78
85
|
|
|
79
86
|
def mode_chosen(self) -> bool:
|
|
80
|
-
"""Return whether
|
|
87
|
+
"""Return whether this learner has answered the audience question.
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
Per project first, then the learner-level answer. The gate asks who
|
|
90
|
+
the *learner* is, so re-asking it on every project would be asking a
|
|
91
|
+
question they have already answered; the header toggle still overrides
|
|
92
|
+
any single project.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
return self._read().get("mode") in _MODES or self._learner_mode() is not None
|
|
84
96
|
|
|
85
97
|
def set_mode(self, mode: str) -> None:
|
|
86
98
|
"""Persist the audience mode beside progress without touching signatures."""
|
|
87
99
|
|
|
88
|
-
if mode not in
|
|
100
|
+
if mode not in _MODES:
|
|
89
101
|
raise ValueError("Mode must be 'easy' or 'expert'.")
|
|
90
102
|
payload = self._read()
|
|
91
103
|
payload["mode"] = mode
|
|
92
104
|
self._write(payload)
|
|
105
|
+
self._write_learner_mode(mode)
|
|
106
|
+
|
|
107
|
+
def _learner_mode(self) -> str | None:
|
|
108
|
+
"""Read the last audience answered on any project in this data dir."""
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
payload = json.loads(self._learner_path.read_text(encoding="utf-8"))
|
|
112
|
+
except (OSError, json.JSONDecodeError, UnicodeDecodeError):
|
|
113
|
+
return None
|
|
114
|
+
if not isinstance(payload, dict):
|
|
115
|
+
return None
|
|
116
|
+
value = payload.get("mode")
|
|
117
|
+
return value if value in _MODES else None
|
|
118
|
+
|
|
119
|
+
def _write_learner_mode(self, mode: str) -> None:
|
|
120
|
+
self._root.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
temporary = self._learner_path.with_suffix(f".{os.getpid()}.tmp")
|
|
122
|
+
try:
|
|
123
|
+
temporary.write_text(
|
|
124
|
+
json.dumps({"mode": mode}, indent=2, sort_keys=True) + "\n",
|
|
125
|
+
encoding="utf-8",
|
|
126
|
+
)
|
|
127
|
+
temporary.replace(self._learner_path)
|
|
128
|
+
finally:
|
|
129
|
+
temporary.unlink(missing_ok=True)
|
|
93
130
|
|
|
94
131
|
def selected_entrypoint(self) -> str | None:
|
|
95
132
|
"""Return the learner's persisted Home choice, if one was stored.
|
|
@@ -69,7 +69,7 @@ class ProjectSelector:
|
|
|
69
69
|
def browse(self, path: str | Path | None = None) -> FolderListing:
|
|
70
70
|
"""List non-hidden child directories for one allowed folder."""
|
|
71
71
|
|
|
72
|
-
resolved = self.resolve(path if path is not None else self.
|
|
72
|
+
resolved = self.resolve(path if path is not None else self._default_start())
|
|
73
73
|
if not resolved.is_dir():
|
|
74
74
|
raise ProjectFolderMissing("That folder does not exist.")
|
|
75
75
|
try:
|
|
@@ -97,6 +97,21 @@ class ProjectSelector:
|
|
|
97
97
|
if self._is_inside_root(Path(str(entry["project_root"])).resolve())
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
+
def _default_start(self) -> Path:
|
|
101
|
+
"""Open browsing beside the newest remembered project, else at the root.
|
|
102
|
+
|
|
103
|
+
Projects tend to live as siblings, so starting at the browse root made
|
|
104
|
+
every switch re-walk the same folders. Derived from recents rather than
|
|
105
|
+
stored, so it cannot drift, and it stays inside the same jail because
|
|
106
|
+
``recents`` already filtered by it.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
for entry in self.recents():
|
|
110
|
+
parent = Path(str(entry["project_root"])).parent
|
|
111
|
+
if self._is_inside_root(parent) and parent.is_dir():
|
|
112
|
+
return parent
|
|
113
|
+
return self._root
|
|
114
|
+
|
|
100
115
|
def _require_inside_root(self, path: Path) -> None:
|
|
101
116
|
if not self._is_inside_root(path):
|
|
102
117
|
raise ProjectFolderForbidden(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:Zen Kaku Gothic New;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2)format("woff2"),url(/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff)format("woff")}@font-face{font-family:Shippori Mincho;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2)format("woff2"),url(/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff)format("woff")}@font-face{font-family:Shippori Mincho;font-style:normal;font-display:swap;font-weight:700;src:url(/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2)format("woff2"),url(/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff)format("woff")}:root{--cm-font-display:"Shippori Mincho", "Hiragino Mincho ProN", "Yu Mincho", serif;--cm-font-body:"Zen Kaku Gothic New", "Hiragino Sans", system-ui, sans-serif;--cm-font-mono:"JetBrains Mono", ui-monospace, monospace;--cm-text-xs:.75rem;--cm-text-sm:.875rem;--cm-text-base:1rem;--cm-text-lg:clamp(1.0625rem, .2vw + 1rem, 1.1875rem);--cm-text-xl:clamp(1.25rem, .5vw + 1.1rem, 1.5rem);--cm-text-2xl:clamp(1.5rem, 1vw + 1.25rem, 2rem);--cm-text-3xl:clamp(1.75rem, 2vw + 1.25rem, 3rem);--cm-text-display:clamp(2.25rem, 4.4vw + .9rem, 4.75rem);--cm-space-2xs:.25rem;--cm-space-xs:.5rem;--cm-space-sm:.75rem;--cm-space-md:1rem;--cm-space-lg:2rem;--cm-space-xl:4rem;--cm-space-2xl:6rem;--cm-space-3xl:9rem;--cm-radius:3px;--cm-radius-lg:6px;--cm-rule:1px;--cm-ease:cubic-bezier(.16, .7, .3, 1);--cm-dur-quick:.16s;--cm-dur-reveal:.42s;--cm-dur-slow:.9s;--cm-z-canvas:0;--cm-z-content:1;--cm-z-rail:40;--cm-z-nav:50}:root[data-theme=dark],:root{--cm-ground:#070b1c;--cm-ground-2:#101a3e;--cm-ground-3:#1a2750;--cm-hairline:#24325c;--cm-hairline-soft:#17203f;--cm-ink:#eef2fa;--cm-ink-2:#9aa8c4;--cm-ink-3:#7d8aa8;--cm-star:#e89b2e;--cm-star-high:#f4c46a;--cm-star-low:#3a2a0c;--cm-star-tint:#e89b2e24;--cm-orbit:#82abec;--cm-orbit-fill:#3f6ac0;--cm-orbit-deep:#2b4d96;--cm-orbit-low:#14264a;--cm-on-fill:#f7faff;--cm-sky-far:#070b1c;--cm-sky-near:#16224a}:root[data-theme=light]{--cm-ground:#faf7f0;--cm-ground-2:#fff;--cm-ground-3:#f2eee4;--cm-hairline:#e6e2d6;--cm-hairline-soft:#efece3;--cm-ink:#101a3e;--cm-ink-2:#3c4a74;--cm-ink-3:#5c688c;--cm-star:#7d4a06;--cm-star-high:#a86408;--cm-star-low:#fff3d6;--cm-star-tint:#7d4a061a;--cm-orbit:#2b4d96;--cm-orbit-fill:#2b4d96;--cm-orbit-deep:#1e3a6e;--cm-orbit-low:#e3ecfb;--cm-on-fill:#faf7f0;--cm-sky-far:#e9edf7;--cm-sky-near:#fff}:root{--cm-route-possible:color-mix(in srgb, var(--cm-ink-2) 30%, var(--cm-ink-3));--cm-node-unlit:color-mix(in srgb, var(--cm-ink-3) 78%, var(--cm-ground));--cm-neb-python:#599079;--cm-neb-js:#8b7ea9;--cm-neb-ts:#568baa;--cm-star-halo:#f2efe9;--cm-label-plate:#090e1cd1;--cm-label-ink:#d6dbe7;--cm-sidebar-width:min(19rem, 82vw);--cm-scrim:color-mix(in srgb, var(--cm-ground) 82%, transparent);--cm-error:oklch(72% .16 28);--cm-space-2xs:.125rem;--cm-space-2xl:6rem;--cm-radius:.25rem;--cm-text-xs:.75rem;--cm-text-sm:.875rem;--cm-text-base:1rem;--cm-text-lg:1.25rem;--cm-text-display:clamp(1.75rem, 3vw + 1rem, 3.25rem);--cm-z-base:1;--cm-z-raised:10;--cm-z-sticky:200;--cm-z-panel:400}*{box-sizing:border-box}html,body,#root{width:100%;min-height:100%;margin:0;overflow-x:clip}body{background:var(--cm-ground);color:var(--cm-ink);font-family:var(--cm-font-body);font-size:var(--cm-text-base);line-height:1.55}button,output{font:inherit}button:focus-visible,[href]:focus-visible,[tabindex]:focus-visible,input:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:3px}.app-shell{background:var(--cm-ground);width:100%;height:100dvh;min-height:100dvh;color:var(--cm-ink);grid-template-rows:auto minmax(0,1fr) auto auto;grid-template-columns:minmax(0,1fr);display:grid;position:relative;overflow:hidden}.instrument-rail{z-index:var(--cm-z-sticky);align-items:center;gap:var(--cm-space-sm) var(--cm-space-md);min-width:0;max-width:100%;padding:var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-left));border-block-end:1px solid var(--cm-hairline);background:var(--cm-ground);grid-row:1;grid-template-columns:minmax(0,1fr) auto;display:grid;position:relative}.brand-lockup{align-items:center;gap:var(--cm-space-sm);min-width:0;display:flex}.brand-lockup div{min-width:0;display:grid}.brand-lockup strong{font-family:var(--cm-font-display);letter-spacing:-.02em;font-weight:700}.brand-lockup span:not(.brand-mark),.home-readout,.location{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.brand-mark{width:var(--cm-space-md);height:var(--cm-space-md);border:1px solid var(--cm-ink-2);background:var(--cm-ink);border-radius:50%}.location{grid-area:2/1/auto/-1;margin:0}.rail-overflow{grid-area:1/2;justify-self:end}.mobile-menu-trigger{min-width:44px;min-height:44px;padding-inline:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-orbit);cursor:pointer;place-items:center;font-weight:600;display:grid}.rail-overflow__panel{z-index:var(--cm-z-panel);gap:var(--cm-space-md);max-height:calc(100dvh - 5rem);padding:var(--cm-space-md);border-block:1px solid var(--cm-hairline);background:var(--cm-ground-2);display:none;position:absolute;inset-block-start:100%;inset-inline:0;overflow-y:auto;box-shadow:0 1rem 2.5rem #00000061}.rail-overflow[data-open] .rail-overflow__panel{display:grid}.instrument-rail:has(.rail-overflow[data-open]){z-index:var(--cm-z-panel)}.rail-overflow__panel .rail-actions,.rail-overflow__panel .rail-controls{grid-area:auto;justify-content:start}.rail-action{min-height:44px;padding-inline:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;white-space:nowrap;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease), transform .1s var(--cm-ease);font-weight:600}@media (hover:hover) and (pointer:fine){.rail-action:hover{background:var(--cm-orbit-low)}}.rail-action:active{transform:translateY(1px)}.rail-action:disabled,.rail-action[aria-disabled=true]{opacity:.5;cursor:not-allowed}.rail-action[data-state=loading]{color:var(--cm-ink-2)}.rail-action[data-state=error]{border-color:var(--cm-error)}.rail-action[data-state=success]{border-color:var(--cm-star);color:var(--cm-star)}.rail-actions{justify-content:end;align-items:center;gap:var(--cm-space-sm);flex-wrap:wrap;grid-area:2/1/auto/-1;min-width:0;display:flex}.rail-controls{align-items:center;gap:var(--cm-space-md);flex-wrap:wrap;grid-area:4/1/auto/-1;min-width:0;display:flex}.switch-project{display:contents}.switch-project[data-confirming]{gap:var(--cm-space-xs);padding:var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);display:grid}.switch-project[data-confirming] p{max-width:26ch;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin:0}.switch-project__error{color:var(--cm-error)!important}.switch-project[data-confirming]>div{gap:var(--cm-space-xs);display:flex}.language-focus{align-items:center;gap:var(--cm-space-sm);min-width:0;color:var(--cm-ink-3);scrollbar-width:none;display:flex;overflow-x:auto}.language-focus::-webkit-scrollbar{display:none}.language-focus__label{font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;flex:none}.language-focus>div{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);min-width:max-content;display:flex}.language-focus button{justify-content:center;align-items:center;gap:var(--cm-space-xs);min-width:44px;min-height:44px;padding-inline:var(--cm-space-sm);border:0;border-inline-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:nowrap;background:0 0;display:inline-flex}.language-focus button:last-child{border-inline-end:0}.language-focus button[aria-pressed=true]{background:var(--cm-orbit-low);color:var(--cm-orbit)}.language-focus button small{color:var(--cm-ink-3);font-size:inherit;font-variant-numeric:tabular-nums}@media (hover:hover) and (pointer:fine){.language-focus button:hover{background:var(--cm-ground-2);color:var(--cm-ink)}.language-focus button[aria-pressed=true]:hover{background:var(--cm-orbit-low);color:var(--cm-orbit)}}.mode-toggle{align-items:center;gap:var(--cm-space-sm);border:0;grid-area:4/1/auto/-1;min-width:0;margin:0;padding:0;display:flex}.mode-toggle__label{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;flex:none}.mode-toggle__options{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);min-width:max-content;display:flex}.mode-toggle__options label{align-items:center;gap:var(--cm-space-2xs);min-width:44px;min-height:44px;padding-inline:var(--cm-space-sm);border-inline-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);display:inline-flex}.mode-toggle__options label:last-child{border-inline-end:0}.mode-toggle__options label:has(input:checked){background:var(--cm-orbit-low);color:var(--cm-orbit)}.mode-toggle__options input{width:1rem;height:1rem;accent-color:var(--cm-orbit);margin:0}@media (hover:hover) and (pointer:fine){.mode-toggle__options label:hover{background:var(--cm-ground-2);color:var(--cm-ink)}.mode-toggle__options label:has(input:checked):hover{background:var(--cm-orbit-low);color:var(--cm-orbit)}}.mode-gate{width:min(26rem, calc(100% - var(--cm-space-xl)));max-height:min(86%,30rem);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;margin:auto;overflow:auto}.mode-gate::backdrop{background:var(--cm-scrim)}.mode-gate h1{overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);margin:0;font-style:normal;line-height:1.15}.mode-gate p{max-width:42ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink-2)}.mode-gate__options{border-block-start:1px solid var(--cm-hairline);margin-block-start:var(--cm-space-lg);display:grid}.mode-gate__options button{min-height:52px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;font:inherit;text-align:start;background:0 0;font-weight:600}.mode-gate__options button:last-child{border-block-end:0}@media (hover:hover) and (pointer:fine){.mode-gate__options button:hover{color:var(--cm-orbit)}}.map-stage{background:var(--cm-ground);grid-row:2;min-width:0;max-width:100%;min-height:0;position:relative;overflow:clip}.chart-stage{background:var(--cm-ground);grid-row:2;min-width:0;min-height:0;position:relative;overflow:hidden}.chart-stage .star-chart-screen{height:100%}.galaxy-frame,.galaxy-canvas{z-index:var(--cm-z-base);position:absolute;inset:0}.galaxy-frame{overflow:hidden}.galaxy-canvas canvas{display:block}.keyboard-focus{max-width:min(34rem, calc(100% - var(--cm-space-lg)));padding:var(--cm-space-sm) var(--cm-space-md);border-inline-start:2px solid var(--cm-orbit);background:var(--cm-ground-2);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);position:absolute;inset-block-end:var(--cm-space-md);inset-inline-start:var(--cm-space-md)}.orientation-bar{z-index:var(--cm-z-raised);gap:var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);pointer-events:none;flex-wrap:wrap;margin:0;display:flex;position:absolute;inset-block-start:var(--cm-space-md);inset-inline-start:max(var(--cm-space-md), env(safe-area-inset-left))}.orientation-bar>*+:before{content:"· ";color:var(--cm-hairline)}.orientation-bar__charted{color:var(--cm-ink-2)}.index-sidebar{z-index:var(--cm-z-panel);width:var(--cm-sidebar-width);border-inline-end:1px solid var(--cm-hairline);background:var(--cm-ground-2);flex-direction:column;display:flex;position:absolute;inset-block:0;inset-inline-start:0}@media (width>=60rem){:is(.map-stage:has(.index-sidebar) .galaxy-frame,.map-stage:has(.index-sidebar) .map-view){inset-inline-start:var(--cm-sidebar-width)}:is(.map-stage:has(.index-sidebar) .orientation-bar,.map-stage:has(.index-sidebar) .orientation-copy){inset-inline-start:calc(var(--cm-sidebar-width) + var(--cm-space-md))}}.index-sidebar>header{justify-content:space-between;align-items:center;gap:var(--cm-space-sm);padding:var(--cm-space-sm) var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);display:flex}.index-sidebar h2{color:var(--cm-ink);font-size:var(--cm-text-sm);letter-spacing:.08em;text-transform:uppercase;margin:0}.index-sidebar>header button{border-radius:var(--cm-radius);min-width:32px;min-height:32px;color:var(--cm-ink-2);font-size:var(--cm-text-lg);cursor:pointer;background:0 0;border:1px solid #0000;line-height:1}.index-sidebar>header button:hover,.index-sidebar>header button:focus-visible{border-color:var(--cm-orbit);color:var(--cm-ink)}.index-sidebar__scroll{flex:1;padding-block-end:var(--cm-space-lg);overflow-y:auto}.index-sidebar section{padding-block-start:var(--cm-space-sm)}.index-sidebar h3{padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-overflow:ellipsis;white-space:nowrap;margin:0;font-weight:400;overflow:hidden}.index-sidebar ul{margin:0;padding:0;list-style:none}.index-sidebar li button{align-items:baseline;gap:var(--cm-space-sm);width:100%;min-height:32px;padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-align:start;cursor:pointer;background:0 0;border:0;border-inline-start:2px solid #0000;display:flex}.index-sidebar li button span{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.index-sidebar li button:hover,.index-sidebar li button:focus-visible{border-inline-start-color:var(--cm-orbit);background:var(--cm-ground);color:var(--cm-ink)}.index-sidebar li button[aria-current=true]{border-inline-start-color:var(--cm-orbit);color:var(--cm-ink)}.index-sidebar li button[data-lit] span{color:var(--cm-star-high)}.index-sidebar li em{color:var(--cm-ink-3);font-size:var(--cm-text-xs);font-style:normal}.module-finder{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);width:min(34rem,92vw);max-height:70vh;color:var(--cm-ink);padding:0}.module-finder::backdrop{background:var(--cm-scrim)}.module-finder input{width:100%;padding:var(--cm-space-md);border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-base);background:0 0}.module-finder input:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:-2px}.module-finder ul{max-height:50vh;padding:var(--cm-space-2xs) 0;margin:0;list-style:none;overflow-y:auto}.module-finder li button{align-items:baseline;gap:var(--cm-space-sm);width:100%;min-height:36px;padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-align:start;cursor:pointer;background:0 0;border:0;border-inline-start:2px solid #0000;display:flex}.module-finder li button[data-active]{border-inline-start-color:var(--cm-orbit);background:var(--cm-ground);color:var(--cm-ink)}.module-finder li strong{font-weight:400}.module-finder li small{color:var(--cm-ink-3);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.module-finder li em{color:var(--cm-star-high);font-style:normal}.module-finder__empty{padding:var(--cm-space-md);color:var(--cm-ink-3);font-size:var(--cm-text-sm);margin:0}.rail-action kbd{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin-inline-start:var(--cm-space-2xs)}.rail-action--toggle[aria-pressed=true]{border-color:var(--cm-orbit);color:var(--cm-orbit)}.orientation-copy{z-index:var(--cm-z-raised);max-width:min(28rem, calc(100% - var(--cm-space-lg)));pointer-events:none;position:absolute;inset-block-start:clamp(var(--cm-space-lg), 8vh, var(--cm-space-2xl));inset-inline-start:max(var(--cm-space-md), env(safe-area-inset-left))}.orientation-copy--system{inset-block-start:var(--cm-space-lg)}.orientation-copy h1,.study-preview h1,.load-state h1,.webgl-error h1{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-display);letter-spacing:-.035em;margin:0;font-style:normal;font-weight:700;line-height:1.08}.orientation-copy p,.study-preview p{max-width:55ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink-2)}.orientation-copy--inline{z-index:auto;pointer-events:auto;flex:none;max-block-size:45%;position:static;overflow-y:auto}.orientation-copy--inline h1{font-size:var(--cm-text-lg)}.orientation-copy--inline p{font-size:var(--cm-text-sm);margin-block-start:var(--cm-space-2xs)}.orientation-copy--inline .check-launch{margin-block-start:var(--cm-space-sm)}.orientation-copy__actions{gap:var(--cm-space-sm);flex-wrap:wrap;align-items:center;display:flex}.check-launch--read{border-color:var(--cm-hairline);color:var(--cm-ink)}.check-launch--read:hover,.check-launch--read:focus-visible{border-color:var(--cm-orbit);color:var(--cm-orbit)}.check-launch,.check-primary,.check-close{border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);min-height:44px;color:var(--cm-orbit);cursor:pointer;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease), transform .1s var(--cm-ease);font-weight:600}.check-launch{padding-inline:var(--cm-space-md);pointer-events:auto;margin-block-start:var(--cm-space-md);position:relative}.check-primary{padding-inline:var(--cm-space-lg);justify-self:start}.check-close{padding-inline:var(--cm-space-md);border-color:var(--cm-hairline);color:var(--cm-ink-2)}@media (hover:hover) and (pointer:fine){.check-launch:hover,.check-primary:hover,.check-close:hover{background:var(--cm-orbit-low);color:var(--cm-ink)}}.check-launch:active,.check-primary:active,.check-close:active{transform:translateY(1px)}.check-primary:disabled{opacity:.45;cursor:not-allowed;transform:none}.legend-toggle{z-index:var(--cm-z-sticky);min-height:32px;padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;position:absolute;inset-block-start:var(--cm-space-md);inset-inline-end:var(--cm-space-md)}.legend-toggle:hover,.legend-toggle:focus-visible{border-color:var(--cm-orbit);color:var(--cm-ink)}.legend-toggle[aria-expanded=true]{border-color:var(--cm-orbit);color:var(--cm-orbit)}.map-legend{z-index:var(--cm-z-raised);gap:var(--cm-space-2xs) var(--cm-space-md);max-height:min(60vh,30rem);padding:var(--cm-space-sm) var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);grid-template-columns:repeat(2,auto);display:none;position:absolute;inset-block-start:calc(var(--cm-space-md) + 40px);inset-inline-end:var(--cm-space-md);overflow-y:auto}.map-legend span{align-items:center;gap:var(--cm-space-sm);white-space:nowrap;display:inline-flex}.legend-dot{width:var(--cm-space-sm);height:var(--cm-space-sm);border-radius:50%;display:inline-block}.legend-dot--dim{background:var(--cm-ink-3)}.legend-dot--lit{background:var(--cm-star-high)}.legend-dot--partial{background:var(--cm-route-possible)}.legend-route{width:var(--cm-space-md);border-block-start:1px solid var(--cm-ink-3)}.legend-route--possible{border-block-start-color:var(--cm-route-possible)}.partial-summary{border-inline-start:2px solid var(--cm-route-possible);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);padding-inline-start:var(--cm-space-sm);color:var(--cm-ink)!important}.entrypoint-picker{z-index:var(--cm-z-panel);width:min(36rem, calc(100vw - var(--cm-space-xl)));max-height:min(80dvh,42rem);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);background:var(--cm-ground-2);color:var(--cm-ink);animation:entrypoint-enter var(--cm-dur-reveal) var(--cm-ease) both}.entrypoint-picker[open]{flex-direction:column;display:flex;overflow:hidden}.entrypoint-picker::backdrop{background:color-mix(in srgb, var(--cm-ground) 88%, transparent)}.entrypoint-picker>p:first-child{color:var(--cm-orbit);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin:0}.entrypoint-picker h1{margin:var(--cm-space-sm) 0 0;overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);line-height:1.1}.entrypoint-picker>p:not(:first-child){margin:var(--cm-space-md) 0 0;color:var(--cm-ink-2)}.entrypoint-count{margin-block:var(--cm-space-md) 0;color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.entrypoint-scroll{min-height:0;border-block-start:1px solid var(--cm-hairline);flex:auto;margin-block-start:var(--cm-space-sm);overflow-y:auto}.entrypoint-scroll details{border-block-end:1px solid var(--cm-hairline)}.entrypoint-scroll summary{justify-content:space-between;align-items:baseline;gap:var(--cm-space-sm);padding:var(--cm-space-sm) 0;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;display:flex}.entrypoint-scroll summary small{color:var(--cm-ink-3)}.entrypoint-candidates{border-block-start:1px solid var(--cm-hairline)}.entrypoint-candidates button{gap:var(--cm-space-xs);width:100%;min-height:58px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.entrypoint-candidates button span{overflow-wrap:anywhere;font-family:var(--cm-font-mono)}.entrypoint-candidates button small{color:var(--cm-ink-3);font-family:var(--cm-font-mono)}@media (hover:hover) and (pointer:fine){.entrypoint-candidates button:hover{color:var(--cm-orbit)}}.entrypoint-continue{width:100%;min-height:44px;padding:var(--cm-space-xs) 0 0;border:0;border-block-start:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;text-underline-offset:3px;background:0 0;flex:none;margin-block-start:var(--cm-space-md);text-decoration:underline}.entrypoint-error{color:var(--cm-error)!important}.illumination-pulse{z-index:var(--cm-z-raised);color:var(--cm-star);pointer-events:none;animation:illumination-pulse .42s var(--cm-ease) both;justify-items:center;display:grid;position:absolute;inset-block-start:50%;inset-inline-start:25%;transform:translate(-50%,-50%)}.illumination-pulse span{filter:drop-shadow(0 0 18px var(--cm-star));font-size:clamp(3rem,8vw,6rem);line-height:1}.illumination-pulse strong{color:var(--cm-ink);font-family:var(--cm-font-display)}.structural-summary p{max-width:62ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink)}.mini-constellation{width:100%;max-width:22rem;height:auto;margin-block-start:var(--cm-space-md)}.mini-constellation line{stroke:var(--cm-hairline);stroke-width:1px}.mini-constellation circle{fill:var(--cm-ink-3)}.mini-constellation__self{fill:var(--cm-orbit)}.connections h3{margin:var(--cm-space-lg) 0 0;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;font-weight:500}.connection-list{margin:var(--cm-space-xs) 0 0;border-block-start:1px solid var(--cm-hairline);padding:0;list-style:none}.connection-list button{gap:var(--cm-space-2xs);width:100%;min-height:52px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.connection-name{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.connection-meta{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}@media (hover:hover) and (pointer:fine){.connection-list button:hover .connection-name{color:var(--cm-orbit)}}.load-state .check-primary,.study-notice .check-primary{justify-self:start;margin-block-start:var(--cm-space-md)}.study-preview{z-index:var(--cm-z-panel);width:100%;min-width:0;max-width:100%;max-height:72%;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-lg), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;position:absolute;inset-block-end:0;inset-inline:0;overflow:auto}.check-panel{z-index:var(--cm-z-panel);width:100%;min-width:0;max-width:100%;max-height:76%;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-lg), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;position:absolute;inset-block-end:0;inset-inline:0;overflow:auto}.check-panel__header{justify-content:space-between;align-items:start;gap:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);padding-block-end:var(--cm-space-lg);display:flex}.check-panel__header p,.check-panel__header h1{margin:0}.check-panel__header p,.check-progress span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.check-panel__header h1{overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);margin-block-start:var(--cm-space-xs);line-height:1.1}.check-loading,.check-state,.check-complete,.active-check{margin-block-start:var(--cm-space-lg)}.check-loading,.check-state p,.check-complete p,.active-check fieldset>p{max-width:60ch;color:var(--cm-ink-2)}.check-state h2,.check-complete h2{font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0}.check-state p,.check-complete p{margin:var(--cm-space-sm) 0 0}.check-complete{gap:var(--cm-space-sm);display:grid}.check-complete__star{color:var(--cm-star);font-size:var(--cm-text-display);line-height:1}.check-progress{align-items:center;gap:var(--cm-space-md);grid-template-columns:auto minmax(5rem,1fr);margin-block-end:var(--cm-space-xl);display:grid}.check-progress progress{background:var(--cm-hairline);width:100%;height:2px;color:var(--cm-star);border:0}.check-progress progress::-webkit-progress-bar{background:var(--cm-hairline)}.check-progress progress::-webkit-progress-value{background:var(--cm-star)}.check-progress progress::-moz-progress-bar{background:var(--cm-star)}.active-check{gap:var(--cm-space-lg);display:grid}.active-check fieldset{border:0;min-width:0;margin:0;padding:0}.active-check legend{overflow-wrap:anywhere;max-width:28ch;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);padding:0;font-weight:700;line-height:1.15}.active-check fieldset>p{margin:var(--cm-space-sm) 0 0}.check-options{border-block-start:1px solid var(--cm-hairline);margin-block-start:var(--cm-space-lg)}.check-options label{align-items:center;gap:var(--cm-space-md);min-height:52px;padding-block:var(--cm-space-sm);border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;grid-template-columns:auto minmax(0,1fr);display:grid}.check-options label:has(input:checked){color:var(--cm-ink)}.check-options input{width:1rem;height:1rem;accent-color:var(--cm-star);margin:0}.check-options span{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.check-feedback{gap:var(--cm-space-xs);padding:var(--cm-space-md);border-inline-start:2px solid var(--cm-error);background:var(--cm-ground);color:var(--cm-ink-2);display:grid}.check-feedback strong{color:var(--cm-ink)}.check-feedback span{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.check-affirmation{align-items:baseline;gap:var(--cm-space-xs);margin:0 0 var(--cm-space-lg);border-inline-start:2px solid var(--cm-star);color:var(--cm-star-high);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);padding-inline-start:var(--cm-space-sm);display:flex}.study-preview__path{font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);color:var(--cm-orbit)!important}.study-preview__header{display:block}.study-preview dl{gap:var(--cm-space-md);margin:var(--cm-space-lg) 0 0;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.study-preview dl div{border-block-start:1px solid var(--cm-hairline);padding-block-start:var(--cm-space-sm)}.study-preview dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.study-preview dd{margin:var(--cm-space-2xs) 0 0;font-variant-numeric:tabular-nums}.study-content{gap:var(--cm-space-xl);min-width:0;margin-block-start:var(--cm-space-xl);display:grid}.study-content>*{min-width:0}.study-section-heading{justify-content:space-between;align-items:center;gap:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);padding-block-end:var(--cm-space-sm);display:flex}.study-section-heading h2,.study-notice h2{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal;line-height:1.15}.study-section-heading span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:nowrap}.source-code{width:100%;min-width:0;max-height:22rem;margin:var(--cm-space-md) 0 0;padding:var(--cm-space-sm) 0;border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-ink);counter-reset:source-line;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);tab-size:4;line-height:1.65;list-style:none;overflow:auto}.source-code li{min-width:max-content;padding-inline:var(--cm-space-sm) var(--cm-space-md);grid-template-columns:3.25rem minmax(0,1fr);display:grid}.source-code li:before{content:attr(data-line);color:var(--cm-ink-3);text-align:end;-webkit-user-select:none;user-select:none;padding-inline-end:var(--cm-space-md)}.source-code li:target{background:var(--cm-orbit-low)}.source-code code{font:inherit;white-space:pre}.grounded-explanation>p,.study-notice p,.study-loading,.evidence-list p{max-width:65ch;color:var(--cm-ink-2)}.grounded-explanation>p,.study-notice p,.study-loading{margin:var(--cm-space-md) 0 0}.grounded-explanation h3{margin:var(--cm-space-lg) 0 var(--cm-space-sm);font-family:var(--cm-font-display);font-size:var(--cm-text-base);font-style:normal}.source-citation{color:var(--cm-orbit);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-underline-offset:2px;text-decoration-thickness:1px}.source-citation:active{color:var(--cm-ink)}@media (hover:hover) and (pointer:fine){.source-citation:hover{color:var(--cm-ink)}}.source-citation--button{cursor:pointer;background:0 0;border:0;padding:0}.source-citation--button:disabled{color:var(--cm-ink-3);cursor:not-allowed}.evidence-list{gap:var(--cm-space-sm);margin:0;padding:0;list-style:none;display:grid}.evidence-list li,.study-notice{padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground)}.evidence-list p{margin:0 0 var(--cm-space-xs)}.evidence-list strong{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);margin-block-end:var(--cm-space-xs);display:block}.lens-notes{margin-block-start:var(--cm-space-sm);display:grid}.lens-note{gap:var(--cm-space-sm);min-width:0;padding-block:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);display:grid}.lens-note h3{margin:0 0 var(--cm-space-xs);font-family:var(--cm-font-display);font-size:var(--cm-text-base);font-style:normal}.lens-note p{max-width:65ch;margin:0 0 var(--cm-space-sm);color:var(--cm-ink-2)}.lens-note code{min-width:0;padding:var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:pre;display:block;overflow-x:auto}.star-chart-screen{min-width:0;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-xl) max(var(--cm-space-md), env(safe-area-inset-left));background:var(--cm-ground);overflow:auto}.star-chart-intro{max-width:72rem;margin-inline:auto;padding-block-end:var(--cm-space-xl);display:block}.star-chart-intro>p:first-child{margin:0 0 var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.star-chart-intro h1{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-display);letter-spacing:-.035em;margin:0;font-style:normal;line-height:1.08}.star-chart-intro>p:not(:first-child){max-width:65ch;margin:var(--cm-space-md) 0 0;color:var(--cm-ink-2)}.star-chart-intro dl{gap:var(--cm-space-md);margin:var(--cm-space-xl) 0 0;padding-block:var(--cm-space-md);border-block:1px solid var(--cm-hairline);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.star-chart-intro dt,.concept-row dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.star-chart-intro dd{margin:var(--cm-space-xs) 0 0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);font-variant-numeric:tabular-nums}.concept-ledger{border-block-start:1px solid var(--cm-hairline);max-width:72rem;margin-inline:auto}.concept-row{align-items:center;gap:var(--cm-space-lg);min-width:0;padding-block:var(--cm-space-lg);border-block-end:1px solid var(--cm-hairline);grid-template-columns:minmax(10rem,.7fr) minmax(8rem,1.2fr) auto;display:grid}.concept-row h2{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal}.concept-row>div:first-child>span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.concept-meter{background:var(--cm-hairline);height:2px;overflow:hidden}.concept-meter span{background:var(--cm-star);height:100%;display:block}.concept-row dl{align-items:center;gap:var(--cm-space-lg);margin:0;display:flex}.concept-row dl div{min-width:5rem}.concept-row dd{margin:var(--cm-space-xs) 0 0;font-variant-numeric:tabular-nums}.status-line{z-index:var(--cm-z-sticky);justify-content:space-between;gap:var(--cm-space-2xs) var(--cm-space-md);min-width:0;max-width:100%;padding:var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-sm), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background:var(--cm-ground);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);font-variant-numeric:tabular-nums;flex-wrap:wrap;grid-row:4;display:flex;position:relative}.status-line span{white-space:normal}.status-line span:nth-child(2){display:none}.load-state,.webgl-error{align-content:center;gap:var(--cm-space-md);min-height:100dvh;padding:var(--cm-space-lg);background:var(--cm-ground);color:var(--cm-ink);display:grid}.load-state p,.webgl-error p{max-width:65ch;color:var(--cm-ink-2);margin:0}@keyframes panel-enter{0%{opacity:0;transform:translateY(var(--cm-space-md))}to{opacity:1;transform:translateY(0)}}@keyframes entrypoint-enter{0%{opacity:0;transform:translateY(var(--cm-space-md))}to{opacity:1;transform:translateY(0)}}@keyframes illumination-pulse{0%{opacity:0;transform:translate(-50%,-50%)scale(.75)}45%{opacity:1;transform:translate(-50%,-50%)scale(1.08)}to{opacity:0;transform:translate(-50%,-50%)scale(1)}}@media (width>=40rem){.instrument-rail{grid-template-columns:minmax(0,1fr) auto minmax(0,1fr)}.rail-overflow{display:contents}.mobile-menu-trigger{display:none}.rail-overflow__panel{display:contents;position:static}.location{text-align:center;grid-area:1/2}.rail-action,.home-readout{justify-self:end}.rail-actions{grid-area:1/3}.rail-controls{grid-row:2;justify-self:center}.status-line span{white-space:nowrap;display:inline}.lens-note{gap:var(--cm-space-lg);grid-template-columns:minmax(8rem,.35fr) minmax(0,1fr)}.study-preview{border-block-start:0;border-inline-start:1px solid var(--cm-hairline);width:min(48rem,60%);max-height:none;inset-block:0;inset-inline-start:auto}.check-panel{border-block-start:0;border-inline-start:1px solid var(--cm-hairline);width:min(42rem,56%);max-height:none;inset-block:0;inset-inline-start:auto}}@media (width<=39.999rem){.study-preview,.check-panel{border-block-start:0;max-height:none;inset:0}}@media (width<=48rem){.illumination-pulse{inset-block-start:35%;inset-inline-start:50%}.star-chart-intro dl{grid-template-columns:1fr}.concept-row{gap:var(--cm-space-md);grid-template-columns:1fr}.concept-row dl{justify-content:space-between}}@media (width>=60rem){.map-legend:not([hidden]){display:grid}.chart-stage:has(.index-sidebar) .star-chart-screen{margin-inline-start:var(--cm-sidebar-width)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.15s!important;animation-duration:.15s!important;animation-iteration-count:1!important}.study-preview,.check-panel,.mode-gate{animation-name:panel-enter-reduced}.entrypoint-picker{animation-name:entrypoint-enter-reduced}.illumination-pulse{animation-name:illumination-pulse-reduced}}@keyframes panel-enter-reduced{0%{opacity:0}to{opacity:1}}@keyframes entrypoint-enter-reduced{0%{opacity:0}to{opacity:1}}@keyframes illumination-pulse-reduced{0%{opacity:0}50%{opacity:1}to{opacity:0}}.picker-screen{gap:var(--cm-space-lg);min-height:100vh;padding:var(--cm-space-2xl) var(--cm-space-xl);flex-direction:column;max-width:44rem;margin:0 auto;display:flex}.picker-wordmark{font-family:var(--cm-font-display);font-size:var(--cm-text-sm);letter-spacing:.2em;text-transform:uppercase;color:var(--cm-ink-3)}.picker-subtitle{color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.picker-recents ul,.picker-browser ul{margin:var(--cm-space-sm) 0 0;gap:var(--cm-space-2xs);flex-direction:column;padding:0;list-style:none;display:flex}.picker-recents button,.picker-browser ul button{justify-content:space-between;gap:var(--cm-space-sm);width:100%;padding:var(--cm-space-xs) var(--cm-space-sm);background:var(--cm-ground-2);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);text-align:left;cursor:pointer;display:flex}.picker-recents button:hover,.picker-recents button:focus-visible,.picker-browser ul button:hover,.picker-browser ul button:focus-visible{border-color:var(--cm-orbit)}.picker-recents button{flex-wrap:wrap}.picker-recent-path{overflow-wrap:anywhere;min-width:0}.picker-recent-lit{color:var(--cm-star);white-space:nowrap}.picker-path{font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);color:var(--cm-ink-2);overflow-wrap:anywhere}.picker-scale,.picker-error{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);padding:var(--cm-space-sm);font-size:var(--cm-text-sm)}.picker-error{color:var(--cm-error)}.picker-select{padding:var(--cm-space-xs) var(--cm-space-lg);background:var(--cm-orbit-low);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-ink);font-size:var(--cm-text-base);cursor:pointer;align-self:flex-start}.picker-select:disabled{opacity:.6;cursor:progress}.layer-switcher{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);display:inline-flex;overflow:hidden}.layer-switcher button{padding:var(--cm-space-2xs) var(--cm-space-sm);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0;border:0}.layer-switcher button[aria-pressed=true]{background:var(--cm-orbit-low);color:var(--cm-ink)}.map-view{gap:var(--cm-space-sm);padding:var(--cm-space-md);background:var(--cm-ground);flex-direction:column;display:flex;position:absolute;inset:0;overflow:hidden}.map-tabs{gap:var(--cm-space-xs);flex:none;display:flex}.map-tabs button{padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0}.map-tabs button[aria-pressed=true]{border-color:var(--cm-orbit);color:var(--cm-ink)}.map-canvas{flex-direction:column;flex:auto;min-height:0;display:flex;position:relative}.map-zoom{z-index:var(--cm-z-raised);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);gap:1px;display:flex;position:absolute;inset-block-start:var(--cm-space-sm);inset-inline-end:var(--cm-space-sm)}.map-zoom button{min-width:34px;min-height:30px;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0;border:0}.map-zoom button:hover:not(:disabled),.map-zoom button:focus-visible{color:var(--cm-orbit)}.map-zoom button:disabled{color:var(--cm-ink-3);cursor:default}@media (width<=40rem){.map-tabs{flex-wrap:wrap;padding-inline-end:4.5rem}.map-zoom{align-self:flex-end;margin-block-end:var(--cm-space-xs);position:static}}.map-scroll{cursor:grab;flex:auto;overflow:auto}.map-scroll[data-panning]{cursor:grabbing;-webkit-user-select:none;user-select:none}.map-canvas__sized{min-width:100%}.architecture-map,.workflow-tree{min-height:0;display:block}.architecture-map__edges path{color:var(--cm-hairline);stroke:currentColor;fill:none;stroke-linecap:round;stroke-linejoin:round}.architecture-map__edges path.is-cycle{color:var(--cm-route-possible)}.architecture-map__arrow{color:var(--cm-hairline)}.architecture-map__arrow.is-cycle{color:var(--cm-route-possible)}.workflow-tree__edges path{stroke:var(--cm-hairline);stroke-width:1.2px;fill:none}.architecture-map__box rect{fill:var(--cm-ground-2);stroke:var(--cm-hairline)}.architecture-map__box .box-tint{stroke:none}.architecture-map__box text{fill:var(--cm-ink);font-family:var(--cm-font-mono);font-size:13px}.architecture-map__box .box-meta{fill:var(--cm-ink-3);font-size:11px}.architecture-map__box[data-partial=true] rect:first-of-type{stroke:var(--cm-route-possible)}.architecture-map__box .box-partial{fill:var(--cm-route-possible);stroke:none}.architecture-map__box[data-understood=true] rect:first-of-type{stroke:var(--cm-star-high)}.architecture-map__box[data-home=true] rect:first-of-type{stroke-width:2px}.architecture-map__box[data-reachable=false] rect:first-of-type{stroke-dasharray:4 3}.architecture-map__box:focus-visible rect:first-of-type,.workflow-tree__row:focus-visible circle{outline:2px solid var(--cm-orbit);outline-offset:2px}.architecture-map__box,.workflow-tree__row{cursor:pointer}.workflow-tree__row circle{fill:var(--cm-ink-3)}.workflow-tree__row[data-partial=true] circle,.workflow-tree__row[data-cut] circle{fill:var(--cm-route-possible)}.workflow-tree__row[data-understood=true] circle{fill:var(--cm-star-high)}.workflow-tree__row text{fill:var(--cm-ink);font-family:var(--cm-font-mono);font-size:13px}.workflow-tree__row .row-meta{fill:var(--cm-ink-3);font-size:11px}.architecture-map__box[data-selected=true] rect:first-of-type{outline:2px solid var(--cm-orbit);outline-offset:3px}.workflow-tree__row[data-selected=true] circle{stroke:var(--cm-orbit);stroke-width:2px}.map-note,.map-loading{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);flex:none;margin:0}.map-state{text-align:center;max-width:46ch;color:var(--cm-ink-2);margin:auto}.hint-chip{z-index:var(--cm-z-raised);align-items:center;gap:var(--cm-space-sm);min-width:0;padding:var(--cm-space-sm) var(--cm-space-md);border-block:1px solid var(--cm-orbit);background:var(--cm-ground-2);color:var(--cm-ink);font-size:var(--cm-text-sm);flex-wrap:wrap;grid-row:3;display:flex;position:static}.hint-chip small{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.hint-chip button{padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-orbit);font-size:var(--cm-text-xs);cursor:pointer;background:0 0}.location button{color:var(--cm-orbit);font:inherit;cursor:pointer;background:0 0;border:0;padding:0}.location button[disabled]{color:var(--cm-ink-2);cursor:default}.location button:not([disabled]):hover{text-decoration:underline}.coach-marks{inset-block:auto var(--cm-space-lg);width:min(38ch, calc(100% - var(--cm-space-lg)));padding:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink);margin-inline:auto;inset-inline:0}.coach-marks::backdrop{background:var(--cm-scrim)}.coach-marks__progress{margin:0 0 var(--cm-space-2xs);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.coach-marks h1{margin:0 0 var(--cm-space-xs);font-size:var(--cm-text-lg)}.coach-marks__actions{justify-content:space-between;gap:var(--cm-space-sm);margin-block-start:var(--cm-space-md);display:flex}.coach-skip{color:var(--cm-ink-3);cursor:pointer;background:0 0;border:0}.legend-tint{width:var(--cm-space-sm);height:var(--cm-space-sm);border-radius:2px;display:inline-block}.legend-tint--python{background:var(--cm-neb-python)}.legend-tint--javascript{background:var(--cm-neb-js)}.legend-tint--typescript{background:var(--cm-neb-ts)}.legend-route--dashed{border-block-start-style:dashed}.legend-size{width:var(--cm-space-md);height:var(--cm-space-sm);background:var(--cm-ink-3);border-radius:50%;display:inline-block}.legend-brightness{width:var(--cm-space-md);height:var(--cm-space-sm);background:linear-gradient(90deg, var(--cm-node-unlit), var(--cm-ink-2));display:inline-block}.app-shell[data-mode=easy]{--cm-text-xs:.8125rem;--cm-text-sm:.9375rem;--cm-text-base:1.125rem;--cm-text-lg:1.375rem;--cm-space-sm:1rem;--cm-space-md:1.375rem;--cm-space-lg:2.5rem;font-size:var(--cm-text-base);line-height:1.7}.app-shell[data-mode=easy] .orientation-copy p,.app-shell[data-mode=easy] .study-preview p,.app-shell[data-mode=easy] .grounded-explanation>p,.app-shell[data-mode=easy] .study-notice p,.app-shell[data-mode=easy] .evidence-list p,.app-shell[data-mode=easy] .lens-note p,.app-shell[data-mode=easy] .active-check fieldset>p,.app-shell[data-mode=easy] .check-state p,.app-shell[data-mode=easy] .check-complete p,.app-shell[data-mode=easy] .check-loading{max-width:46ch}.app-shell[data-mode=easy] .language-focus__label,.app-shell[data-mode=easy] .mode-toggle__label{text-transform:none;letter-spacing:normal}.loading-screen{gap:var(--cm-space-md);max-width:44rem;min-height:100vh;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-left));align-content:center;justify-items:start;margin:0 auto;display:grid}.loading-header h1{min-width:0;margin:var(--cm-space-2xs) 0 0;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);overflow-wrap:anywhere;line-height:1.15}.loading-path{font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);color:var(--cm-ink-2)}.loading-subtitle{margin:var(--cm-space-xs) 0 0;color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.loading-stages{gap:var(--cm-space-2xs);width:100%;margin:0;padding:0;list-style:none;display:grid}.loading-stages li{justify-content:space-between;gap:var(--cm-space-sm);padding:var(--cm-space-xs) var(--cm-space-sm);border-left:2px solid var(--cm-hairline);color:var(--cm-ink-3);font-size:var(--cm-text-sm);flex-wrap:wrap;display:flex}.loading-stages li[data-state=active]{border-left-color:var(--cm-orbit);color:var(--cm-ink)}.loading-stages li[data-state=done]{border-left-color:var(--cm-orbit-deep);color:var(--cm-ink-2)}.loading-stages small{font-family:var(--cm-font-mono);color:var(--cm-ink-2)}.loading-meter{width:100%;height:var(--cm-space-xs);accent-color:var(--cm-orbit)}.loading-live{color:var(--cm-ink);font-size:var(--cm-text-sm)}.loading-error{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);width:100%;padding:var(--cm-space-sm);color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.loading-actions{gap:var(--cm-space-sm);flex-wrap:wrap;display:flex}.loading-actions button{padding:var(--cm-space-xs) var(--cm-space-md)}.picker-scale h2,.picker-failure h2{margin:0 0 var(--cm-space-xs);font-family:var(--cm-font-display);font-size:var(--cm-text-base)}.picker-scale-scopes{gap:var(--cm-space-xs);margin:var(--cm-space-sm) 0;flex-wrap:wrap;padding:0;list-style:none;display:flex}.picker-scale-scopes button{align-items:flex-start;gap:var(--cm-space-2xs);min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-sm);background:var(--cm-ground-2);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);cursor:pointer;flex-direction:column;display:flex}.picker-scale-scopes button:hover:not(:disabled),.picker-scale-scopes button:focus-visible{border-color:var(--cm-orbit)}.picker-scale-scopes button:disabled{opacity:.6;cursor:progress}.picker-scale-scopes small{color:var(--cm-ink-2)}.picker-path-entry{gap:var(--cm-space-xs);flex-wrap:wrap;align-items:center;display:flex}.picker-path-entry label{color:var(--cm-ink-2)}.picker-path-entry input{min-width:0;min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-xs);background:var(--cm-ground);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);flex:14rem}.picker-path-entry button{min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-md);background:var(--cm-orbit-low);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-ink);font-size:var(--cm-text-sm);cursor:pointer}.picker-path-entry button:disabled{opacity:.6;cursor:not-allowed}.picker-failure{border:1px solid var(--cm-error);border-radius:var(--cm-radius);padding:var(--cm-space-sm);font-size:var(--cm-text-sm)}.picker-failure>p{margin:0 0 var(--cm-space-sm);color:var(--cm-ink-2)}.picker-failure__detail{gap:var(--cm-space-2xs);display:grid}.picker-failure__detail span{color:var(--cm-ink-3);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em}.picker-failure__detail code{max-height:8rem;padding:var(--cm-space-xs);background:var(--cm-ground-2);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);overflow-wrap:anywhere;display:block;overflow:auto}.picker-failure .picker-select{min-height:44px;font-size:var(--cm-text-sm);align-self:flex-start}.progress-reset{align-items:center;gap:var(--cm-space-sm);max-width:72rem;margin:var(--cm-space-lg) auto 0;padding-top:var(--cm-space-md);border-top:1px solid var(--cm-hairline);flex-wrap:wrap;display:flex}.progress-reset p{color:var(--cm-ink-2);font-size:var(--cm-text-sm);margin:0}.progress-reset button{min-height:44px;padding-inline:var(--cm-space-md);font-size:var(--cm-text-sm)}.progress-reset--confirming{justify-items:start;gap:var(--cm-space-sm);display:grid}.progress-reset--confirming>p[role=alert]{max-width:60ch;color:var(--cm-ink)}.progress-reset--confirming>div{gap:var(--cm-space-sm);flex-wrap:wrap;display:flex}.progress-reset__confirm{border-color:var(--cm-error);color:var(--cm-error)}.progress-reset__error{color:var(--cm-error)}
|