taskops-cli 0.5.2__tar.gz → 0.5.3__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.
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ARCHITECTURE.md +14 -3
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/CHANGELOG.md +26 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/PKG-INFO +1 -1
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_version.py +1 -1
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/stale.py +22 -3
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_topology.py +99 -0
- taskops_cli-0.5.3/ui/smoke/sections/card-diff-range.tsx +119 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/git-diff.tsx +1 -1
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/Patch.tsx +34 -21
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/Sections.tsx +16 -18
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/links.tsx +73 -34
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/pages/WorktreeDiff.tsx +5 -2
- taskops_cli-0.5.2/ui/smoke/sections/card-head-sha.tsx +0 -84
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.claude/settings.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.gitattributes +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.github/workflows/ci.yml +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.github/workflows/workflow.yml +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.gitignore +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.mcp.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/cache.sqlite +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/cache.sqlite-shm +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/cache.sqlite-wal +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/events.jsonl +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/live.sqlite +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/live.sqlite-shm +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board/live.sqlite-wal +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/board.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/reports/lifecycle-chapter.html +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/reports/reports-chapter.html +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/.taskops/ui.lock +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/CLAUDE.md +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/README.md +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/pyproject.toml +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/smoke.sh +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_clock.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_errors.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_ids.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_json.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_locate.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/_wire.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/board.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/__main__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/admin.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/claude.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/commands.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/enrol.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/github.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/gitremote.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/grants.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/hooks.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/main.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/operate.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/paging.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/parser.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/pull.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/push.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/remote.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/rm.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/serving.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/team.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/watch.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/window.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/cli/wording.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/actors.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/challenge.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/chapters.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/event.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/forge.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/graph.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/holding.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/hours.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/kinds.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/machine.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/mentions.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/replay.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/reports.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/review.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/scope.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/seams.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/core/types.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/bare.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/bind.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/catchup.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/claudefiles.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/diff.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/install.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/landing.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/onward.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/patch.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/remote.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/run.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/sig.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/trailer.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/gitwork/trees.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/admin.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/auth.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/feed.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/gitbody.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/gitdoor.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/gitpack.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/grants.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/handler.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/ingest.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/login.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/members.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/mounts.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/page.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/removal.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/repos.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/routes.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/rpc.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/scoped.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/server.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/static.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/upstream.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/http/watcher.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/identity.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/__main__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/activity.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/before.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/boards.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/boardview.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/brief.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/chapter.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/dossier.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/fields.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/gitmoves.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/hello.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/integrate.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/orders.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/render.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/schema.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/server.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/thread.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/mcp/tools.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/py.typed +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/session.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/cache.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/creds.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/handover.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/live.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/log.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/mirroring.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/pubkeys.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/reviews.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/server.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/store/stores.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/ui/app.js +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/ui/index.html +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/ui/style.css +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_args.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_cards.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_chapter.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_context.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_facts.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_mentions.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_rows.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_stories.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_waiting.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/_windows.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/activity.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/assign.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/card.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/events.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/filed.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/plan.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/project.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/pulse.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/record.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/report.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/review.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/take.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/src/taskops/verbs/update.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/__init__.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/conftest.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_architecture.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_claude.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_core.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_git.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_mcp.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_mirror.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_report.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_store.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_ui.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_verbs.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/test_window.py +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/tests/ui_harness.js +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/build.mjs +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/index.html +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/package-lock.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/package.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/fixture.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/main.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/run.mjs +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/actors-window-filter.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/actors.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/all-chapters-scope.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/board-columns.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/board-flow-view.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/board-motion-and-avatars.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/board-root-mount.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/chapter-completion-derivation.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/chapter-landed.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/chapter-story-grid.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/chapters-open.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/comment-box.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/comment-toasts-model.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/comment-toasts-stack.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/diff-page.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/dossier.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/event-stream.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/feed-reconnect.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/forge-links.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/forge-opens-the-board.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/hosted-window.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/monitor-panes.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/overlay-escape.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/report-markdown.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/report-sandbox.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/reviewing-skew.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/section.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/swarm-topology.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/thread-closing-note.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections/worktrees-index.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/smoke/sections.mjs +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/App.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/client.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/completed.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/actors/Daysheet.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/actors/DevPanel.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/board/CardTile.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/board/Column.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/board/ViewToggle.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/board/flip.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/board/useFlip.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/CommentBox.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/Drawer.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/Thread.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/split.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/card/tokens.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/chrome/AvatarStack.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/chrome/Header.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/chrome/KpiRail.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/chrome/MilestonePicker.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/chrome/TabNav.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/flow/FlowView.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/flow/layout.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/Chapter.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/DependencyChain.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/EditSurface.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/EventStream.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/LeaseHealth.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/LiveLeases.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/Mentions.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/Pane.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/Swarm.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/Throughput.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/monitor/panels.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/reports/ReportFrame.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/shared/Avatar.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/shared/Markdown.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/shared/Overlay.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/shared/Segmented.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/shared/overlayStack.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/story/ChapterStory.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/story/stats.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/toasts/ToastStack.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/toasts/model.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/components/toasts/useToasts.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/format.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/hoursWindow.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/main.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/markdown.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/pages/Actors.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/pages/Board.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/pages/Monitor.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/pages/Reports.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/pages/Worktrees.tsx +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/theme/theme.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/theme/tokens.css +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/types.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/useBoard.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/src/useEvents.ts +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/styles/index.css +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/ui/tsconfig.json +0 -0
- {taskops_cli-0.5.2 → taskops_cli-0.5.3}/uv.lock +0 -0
|
@@ -1526,9 +1526,16 @@ below — and comes back only standing on a real history: the forge mirror, in
|
|
|
1526
1526
|
That cascade is ONE function — `ui/src/links.tsx::cascade`, beside the slug and
|
|
1527
1527
|
the link templates it already owned — and `ui/src/components/card/Patch.tsx`
|
|
1528
1528
|
only DRAWS the step it is handed, so no component holds a fallback order of its
|
|
1529
|
-
own. It feeds the dossier's **Files changed** (the card as a PR
|
|
1530
|
-
|
|
1531
|
-
|
|
1529
|
+
own. It feeds the dossier's **Files changed** (the card as a PR, over the range
|
|
1530
|
+
the BOARD recorded rather than two branch names — `links.tsx::cardRange`: one
|
|
1531
|
+
recorded commit goes through `commit/<sha>`, which the door diffs against its
|
|
1532
|
+
first parent, and several become `compare/<first>^...<last>`, whose base is the
|
|
1533
|
+
oldest commit's PARENT so that commit's own changes are inside the range. Both
|
|
1534
|
+
ends are durable, which is what makes the pane render for a card whose `tk-*`
|
|
1535
|
+
and `ms/*` branches were pruned before this host became the remote — the head
|
|
1536
|
+
alone was not enough, 0.5.2 shipped that and the pane stayed empty. Then the
|
|
1537
|
+
file list from `stat`, each file's patch on expand via `?path=`) and the fold on
|
|
1538
|
+
every commit row. Availability is
|
|
1532
1539
|
DISCOVERED, not configured: the first refusal whose words are
|
|
1533
1540
|
`gitdoor.py::NO_REPO` flips a module-level flag for the session and nothing asks
|
|
1534
1541
|
again — an unknown ref does not flip it, because that means "ask again for
|
|
@@ -1542,6 +1549,10 @@ effect firing, and that half is covered against a real server in
|
|
|
1542
1549
|
chapter): `pages/WorktreeDiff.tsx`, the full-width page a Worktrees row opens,
|
|
1543
1550
|
hands its whole range `<milestone branch>...tk-<id>` to the very same
|
|
1544
1551
|
`FilesChanged` — plus a summary bar, which is a prop on it and not a second ask.
|
|
1552
|
+
Two live BRANCHES there, deliberately, where the dossier asks by sha: that page's
|
|
1553
|
+
subject is a working tree that exists on disk right now, so where its two tips
|
|
1554
|
+
stand IS the question. `FilesChanged` therefore takes a whole `GitTarget` rather
|
|
1555
|
+
than a base/head pair — a pair could only ever express the second question.
|
|
1545
1556
|
Nothing else moved to do that: **no verb, no stored key, no change to the door**,
|
|
1546
1557
|
and no component fetches a patch outside `cascade()`. The one seam this cost is
|
|
1547
1558
|
`FileList` exported beside `FilesChanged` — the pure half, given a step — for
|
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
The source of truth for release notes — GitHub Releases are extracted from
|
|
4
4
|
here, never written twice.
|
|
5
5
|
|
|
6
|
+
## 0.5.3 — the Files-changed pane renders for a landed card
|
|
7
|
+
|
|
8
|
+
- **The pane asked for two branch names, and a landed card has neither.**
|
|
9
|
+
0.5.2 made the HEAD durable — the card's newest recorded sha instead of its
|
|
10
|
+
branch — and stopped there. The BASE stayed the chapter's branch name, and
|
|
11
|
+
for every card that landed before this host became the board's remote that
|
|
12
|
+
branch was pruned on the forge, so the compare refused and the pane was
|
|
13
|
+
empty while the commits sat in the trunk. Reported live on tk-bffa26.
|
|
14
|
+
- **The range now comes from the card's own commits, both ends**
|
|
15
|
+
(`ui/src/links.tsx::cardRange`): one recorded commit goes through the commit
|
|
16
|
+
door, which already diffs a commit against its first parent; several become
|
|
17
|
+
`compare/<oldest>^...<newest>`. The base is the oldest commit's PARENT, not
|
|
18
|
+
the oldest commit — `git diff` is exclusive of its base, so `<oldest>...<newest>`
|
|
19
|
+
answers 200 with that commit's own changes silently missing, which is a pane
|
|
20
|
+
that looks complete and is not. No server change was needed: `<sha>^` already
|
|
21
|
+
passed the ref shape and resolves through `^{commit}`.
|
|
22
|
+
- **Pinned where it broke.** The pane renders the route it asked for, and a
|
|
23
|
+
smoke section reads that route off the rendered component — the previous
|
|
24
|
+
attempt was pinned one layer below the call site, so a call-site mutation
|
|
25
|
+
went green while the pane stayed broken. `smoke.sh` now derives the same
|
|
26
|
+
route from the LIVE card payload and asserts the first commit's files are in
|
|
27
|
+
the answer.
|
|
28
|
+
- **"so them was never pushed here"** — the host's missing-ref refusal had a
|
|
29
|
+
plural substitution in a subject slot. The number now runs through the whole
|
|
30
|
+
sentence, and both forms are pinned as bytes.
|
|
31
|
+
|
|
6
32
|
## 0.5.2 — the host holds the git; GitHub is the copy
|
|
7
33
|
|
|
8
34
|
0.5.0 and 0.5.1 built the hosted window on a mirror the host PULLED from the
|
|
@@ -49,12 +49,12 @@ that reader and must not drift toward the host's."""
|
|
|
49
49
|
HOSTED = (
|
|
50
50
|
"{refs} not in this board's own repository on this host. Nothing was "
|
|
51
51
|
"pruned to make that true — this host keeps every branch pushed to it "
|
|
52
|
-
"forever — so {
|
|
53
|
-
"became the board's remote, or a worktree whose commits have not left it. "
|
|
52
|
+
"forever — so {subject} never pushed here: {cause}. "
|
|
54
53
|
"A commit reaches this repository the moment it is made in a worktree "
|
|
55
54
|
"joined to the board; the declared forge is a copy of what is here, never "
|
|
56
55
|
"the other way round, so there is nowhere else this pane could look."
|
|
57
56
|
)
|
|
57
|
+
|
|
58
58
|
"""The HOST's words — a reader with no clone, who can run nothing. No command
|
|
59
59
|
is named because there is none they could run, and no forge is named because
|
|
60
60
|
the forge is no longer a source: under the reversal it is a projection of this
|
|
@@ -62,6 +62,21 @@ repository, so "not here" is not "look over there". What replaced the mirror's
|
|
|
62
62
|
"a pruned branch is normal" is the stronger fact — this host prunes nothing —
|
|
63
63
|
and therefore a different diagnosis: the commits never arrived."""
|
|
64
64
|
|
|
65
|
+
SINGLE = (
|
|
66
|
+
"a card worked before this host became the board's remote, or a "
|
|
67
|
+
"worktree whose commits have not left it"
|
|
68
|
+
)
|
|
69
|
+
SEVERAL = (
|
|
70
|
+
"cards worked before this host became the board's remote, or "
|
|
71
|
+
"worktrees whose commits have not left them"
|
|
72
|
+
)
|
|
73
|
+
"""The DIAGNOSIS, in the number of the refs it explains. It is an apposition
|
|
74
|
+
to the missing refs themselves — "they were never pushed here: cards worked
|
|
75
|
+
before …" — so it declines with them, subject and possessive both: one ref
|
|
76
|
+
missing is one card or one worktree, several are cards or worktrees. 0.5.2
|
|
77
|
+
shipped "so them was never pushed here" with this clause frozen singular; a
|
|
78
|
+
sentence a reader is meant to trust cannot ask them to forgive its grammar."""
|
|
79
|
+
|
|
65
80
|
SHA = "0123456789abcdef"
|
|
66
81
|
|
|
67
82
|
|
|
@@ -82,7 +97,11 @@ def sentence(*refs: str, hosted: bool = False) -> str:
|
|
|
82
97
|
listed = f"{' and '.join(names)} {'are' if many else 'is'}"
|
|
83
98
|
them = "them" if many else "it"
|
|
84
99
|
if hosted:
|
|
85
|
-
return HOSTED.format(
|
|
100
|
+
return HOSTED.format(
|
|
101
|
+
refs=listed,
|
|
102
|
+
subject="they were" if many else "it was",
|
|
103
|
+
cause=SEVERAL if many else SINGLE,
|
|
104
|
+
)
|
|
86
105
|
branches = [ref for ref in names if not _looks_like_a_sha(ref)]
|
|
87
106
|
return STALE.format(
|
|
88
107
|
refs=listed,
|
|
@@ -2101,6 +2101,105 @@ def test_the_hosts_missing_ref_sentence_says_what_a_host_that_prunes_nothing_can
|
|
|
2101
2101
|
assert "Nothing was pruned" in message and "never pushed here" in message
|
|
2102
2102
|
|
|
2103
2103
|
|
|
2104
|
+
# ── the CARD'S OWN RANGE, asked by sha (ui/src/links.tsx::cardRange) ───────
|
|
2105
|
+
|
|
2106
|
+
|
|
2107
|
+
def test_a_landed_cards_own_commits_name_a_range_this_host_can_read(
|
|
2108
|
+
server: BoardServer, tmp_path: Path
|
|
2109
|
+
) -> None:
|
|
2110
|
+
"""The reported failure, end to end and on the SERVER side of it.
|
|
2111
|
+
|
|
2112
|
+
A card's branches — its own and its chapter's — were pruned on the forge
|
|
2113
|
+
before this host became the board's remote, so neither name resolves here
|
|
2114
|
+
while both of the card's commits sit in the trunk that was pushed. The
|
|
2115
|
+
dossier therefore asks by sha, and for a multi-commit card the base is the
|
|
2116
|
+
FIRST commit's PARENT: `<sha>^`, a spelling `gitwork/diff.py` already
|
|
2117
|
+
resolves (`SHAPE` admits `^`, `resolve()` peels `^{commit}`), which is why
|
|
2118
|
+
this chapter added nothing to the door.
|
|
2119
|
+
|
|
2120
|
+
The last assertion is the one worth having: with the first SHA as the base
|
|
2121
|
+
instead of its parent, the door still answers 200 and the pane still looks
|
|
2122
|
+
complete — with that commit's file silently missing. That is the failure
|
|
2123
|
+
mode the `^` exists to prevent, and it is pinned here as the difference
|
|
2124
|
+
between the two ranges rather than described.
|
|
2125
|
+
"""
|
|
2126
|
+
from urllib.parse import quote
|
|
2127
|
+
|
|
2128
|
+
from taskops.gitwork import bare
|
|
2129
|
+
from tests.test_mirror import run, commit, forge_repo
|
|
2130
|
+
|
|
2131
|
+
src = forge_repo(tmp_path / "fixture")
|
|
2132
|
+
run.must("switch", "-q", "-c", "tk-range", cwd=src)
|
|
2133
|
+
first = commit(src, "one.py")
|
|
2134
|
+
last = commit(src, "two.py")
|
|
2135
|
+
run.must("switch", "-q", "main", cwd=src)
|
|
2136
|
+
run.must("merge", "--no-ff", "-q", "-m", "land tk-range", "tk-range", cwd=src)
|
|
2137
|
+
repo = bare.ensure(server.mounts.root / BOARD)
|
|
2138
|
+
run.must("push", str(repo), "main:refs/heads/main", cwd=src)
|
|
2139
|
+
token = _token(server, BERNA)
|
|
2140
|
+
|
|
2141
|
+
status, _ = _get(f"{url_of(server)}/git/commit/tk-range", token)
|
|
2142
|
+
assert status == 404, "the branch must be absent, or this proves nothing"
|
|
2143
|
+
|
|
2144
|
+
# percent-encoded, as `links.tsx::gitRoute` sends it: `...` is the route's
|
|
2145
|
+
# grammar and stays literal, everything else is one encoded ref.
|
|
2146
|
+
base = quote(f"{first}^", safe="")
|
|
2147
|
+
status, body = _get(f"{url_of(server)}/git/compare/{base}...{last}", token)
|
|
2148
|
+
assert status == 200, body
|
|
2149
|
+
assert set(body["data"]["stat"]) == {"one.py", "two.py"}
|
|
2150
|
+
|
|
2151
|
+
status, dropped = _get(f"{url_of(server)}/git/compare/{first}...{last}", token)
|
|
2152
|
+
assert status == 200 and set(dropped["data"]["stat"]) == {"two.py"}
|
|
2153
|
+
def test_the_hosts_missing_ref_sentence_is_grammatical_for_one_ref(
|
|
2154
|
+
server: BoardServer, tmp_path: Path
|
|
2155
|
+
) -> None:
|
|
2156
|
+
"""The SINGULAR half, whole. 0.5.2 served "so them was never pushed here"
|
|
2157
|
+
on the live window: a plural substitution in a subject slot. The number
|
|
2158
|
+
now runs through the whole sentence — subject, verb and the diagnosis it
|
|
2159
|
+
introduces — so both halves are pinned as bytes, not as substrings."""
|
|
2160
|
+
_own_git(server, tmp_path / "fixture")
|
|
2161
|
+
status, body = _get(
|
|
2162
|
+
f"{url_of(server)}/git/commit/tk-dfaff7", _token(server, BERNA)
|
|
2163
|
+
)
|
|
2164
|
+
assert status == 404
|
|
2165
|
+
assert body["error"]["message"] == (
|
|
2166
|
+
"tk-dfaff7 is not in this board's own repository on this host. "
|
|
2167
|
+
"Nothing was pruned to make that true — this host keeps every branch "
|
|
2168
|
+
"pushed to it forever — so it was never pushed here: a card worked "
|
|
2169
|
+
"before this host became the board's remote, or a worktree whose "
|
|
2170
|
+
"commits have not left it. A commit reaches this repository the moment "
|
|
2171
|
+
"it is made in a worktree joined to the board; the declared forge is a "
|
|
2172
|
+
"copy of what is here, never the other way round, so there is nowhere "
|
|
2173
|
+
"else this pane could look."
|
|
2174
|
+
)
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
def test_the_hosts_missing_ref_sentence_is_grammatical_for_several_refs(
|
|
2178
|
+
server: BoardServer, tmp_path: Path
|
|
2179
|
+
) -> None:
|
|
2180
|
+
"""The PLURAL half — the one that was wrong. A compare asks for two refs
|
|
2181
|
+
at once, which is exactly how the live report arrived: base ms/… and head
|
|
2182
|
+
tk-… both absent. "they were never pushed here: cards worked … or
|
|
2183
|
+
worktrees whose commits have not left them" — read it aloud; that is the
|
|
2184
|
+
contract."""
|
|
2185
|
+
_own_git(server, tmp_path / "fixture")
|
|
2186
|
+
status, body = _get(
|
|
2187
|
+
f"{url_of(server)}/git/compare/ms/a-chapter...tk-bffa26",
|
|
2188
|
+
_token(server, BERNA),
|
|
2189
|
+
)
|
|
2190
|
+
assert status == 404
|
|
2191
|
+
assert body["error"]["message"] == (
|
|
2192
|
+
"ms/a-chapter and tk-bffa26 are not in this board's own repository on "
|
|
2193
|
+
"this host. Nothing was pruned to make that true — this host keeps "
|
|
2194
|
+
"every branch pushed to it forever — so they were never pushed here: "
|
|
2195
|
+
"cards worked before this host became the board's remote, or "
|
|
2196
|
+
"worktrees whose commits have not left them. A commit reaches this "
|
|
2197
|
+
"repository the moment it is made in a worktree joined to the board; "
|
|
2198
|
+
"the declared forge is a copy of what is here, never the other way "
|
|
2199
|
+
"round, so there is nowhere else this pane could look."
|
|
2200
|
+
)
|
|
2201
|
+
|
|
2202
|
+
|
|
2104
2203
|
# ── the HOSTED window: /ui on a serve-mode host, per repo and visibility ───
|
|
2105
2204
|
|
|
2106
2205
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* The card's Files-changed pane asks for a range that survives its chapter
|
|
2
|
+
* landing — BOTH ends of it, and pinned where the pane itself chooses it.
|
|
3
|
+
*
|
|
4
|
+
* WHAT WENT WRONG TWICE. First version: base = the chapter's branch, head = the
|
|
5
|
+
* card's branch — and for a landed card both were pruned on the forge before
|
|
6
|
+
* this host became the board's remote, so the compare refused and the pane was
|
|
7
|
+
* empty. Second version made only the HEAD a recorded sha; the base stayed a
|
|
8
|
+
* branch name and the pane stayed empty. Reported live on tk-bffa26 both times.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS FILE INSISTS ON THE CALL SITE. The previous section pinned the
|
|
11
|
+
* decision as a pure function and said so honestly: `FilesChanged` fetches
|
|
12
|
+
* through a `useEffect`, `react-dom/server` fires none, and a mutation at the
|
|
13
|
+
* dossier's call site was measured to be INVISIBLE here. So the fix that
|
|
14
|
+
* shipped was pinned everywhere except in the one component that was broken.
|
|
15
|
+
* `FilesChanged` now renders the route it asked for as `data-range` on its
|
|
16
|
+
* wrapper, which is a fact about the pane and not a test hook, and every claim
|
|
17
|
+
* below reads it off a rendered `<Body>` — the real component, the real
|
|
18
|
+
* payload. Mutate the call site and this section goes red.
|
|
19
|
+
*
|
|
20
|
+
* The one-commit dossier is the fixture's own card (`tests/test_ui.py`), so the
|
|
21
|
+
* shape asserted is the shape the SERVER sends on `CardPayload.commits`. The
|
|
22
|
+
* multi-commit one is that card with a second commit appended, which is the
|
|
23
|
+
* shape tk-bffa26's live payload has: two bodies, oldest first, both with a
|
|
24
|
+
* `branch` the host no longer carries.
|
|
25
|
+
*/
|
|
26
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
27
|
+
|
|
28
|
+
import { Body } from "../../src/components/card/Sections";
|
|
29
|
+
import { cardRange, gitForge, gitRoute } from "../../src/links";
|
|
30
|
+
import type { CardPayload } from "../../src/types";
|
|
31
|
+
import type { Check, Fixture, Harness } from "./section";
|
|
32
|
+
|
|
33
|
+
/** The pane's own `data-range`, off a rendered dossier body. */
|
|
34
|
+
function asked(dossier: CardPayload, now: number): string {
|
|
35
|
+
const markup = renderToStaticMarkup(<Body dossier={dossier} now={now} repo={null} reader={null} />);
|
|
36
|
+
return /data-range="([^"]*)"/.exec(markup)?.[1] ?? "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function run(fixture: Fixture, check: Check, h: Harness): Promise<void> {
|
|
40
|
+
const { now, REPO } = h;
|
|
41
|
+
const one = fixture.card;
|
|
42
|
+
const commits = one.commits;
|
|
43
|
+
|
|
44
|
+
check(
|
|
45
|
+
"the dossier payload carries a per-card sha at all",
|
|
46
|
+
commits.length > 0 && typeof commits[0]?.sha === "string",
|
|
47
|
+
JSON.stringify(commits.map((c) => c.sha)),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
/* ── one recorded commit: the commit door, which diffs against parent #1 ── */
|
|
51
|
+
const single: CardPayload = { ...one, commits: [commits[0]!] };
|
|
52
|
+
const first = commits[0]!.sha;
|
|
53
|
+
check(
|
|
54
|
+
"a one-commit card asks the COMMIT door for its own sha",
|
|
55
|
+
asked(single, now) === `git/commit/${encodeURIComponent(first)}`,
|
|
56
|
+
asked(single, now),
|
|
57
|
+
);
|
|
58
|
+
check(
|
|
59
|
+
"and never a branch name on either side",
|
|
60
|
+
!asked(single, now).includes(one.branch) && !asked(single, now).includes("ms/"),
|
|
61
|
+
asked(single, now),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/* ── several: the whole span, based on the FIRST commit's PARENT ────────── */
|
|
65
|
+
const last = "f".repeat(40);
|
|
66
|
+
const landed: CardPayload = {
|
|
67
|
+
...one,
|
|
68
|
+
commits: [commits[0]!, { ...commits[0]!, sha: last, subject: "the second half" }],
|
|
69
|
+
};
|
|
70
|
+
const span = asked(landed, now);
|
|
71
|
+
check(
|
|
72
|
+
"a multi-commit card asks compare(<first>^ ... <last>) — the parent, not the first commit",
|
|
73
|
+
span === `git/compare/${encodeURIComponent(`${first}^`)}...${encodeURIComponent(last)}`,
|
|
74
|
+
span,
|
|
75
|
+
);
|
|
76
|
+
check(
|
|
77
|
+
"so the oldest commit's own changes are INSIDE the range, not one off its edge",
|
|
78
|
+
span.includes(encodeURIComponent(`${first}^`)) && !span.includes(`git/compare/${encodeURIComponent(first)}...`),
|
|
79
|
+
span,
|
|
80
|
+
);
|
|
81
|
+
check(
|
|
82
|
+
"and the landed card's pane asks for no branch this host may have pruned",
|
|
83
|
+
!span.includes(one.branch) && !span.includes("ms"),
|
|
84
|
+
span,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
/* ── the fallback, and the forge link beside the pane ───────────────────── */
|
|
88
|
+
check(
|
|
89
|
+
"a commit body with no sha falls to the two branch names",
|
|
90
|
+
JSON.stringify(cardRange([{}], "tk-dfaff7", "ms/x")) ===
|
|
91
|
+
JSON.stringify({ kind: "compare", base: "ms/x", head: "tk-dfaff7" }),
|
|
92
|
+
);
|
|
93
|
+
check(
|
|
94
|
+
"no commit bound at all is no section — the gate the pane always had",
|
|
95
|
+
cardRange([], "tk-dfaff7", "ms/x") === null && cardRange(undefined, "tk-x", "ms/x") === null,
|
|
96
|
+
);
|
|
97
|
+
check(
|
|
98
|
+
"so a dossier with no commits renders no Files-changed pane at all",
|
|
99
|
+
asked({ ...one, commits: [] }, now) === "",
|
|
100
|
+
);
|
|
101
|
+
const forgeOne = gitForge(REPO, cardRange([{ sha: first }], "tk-x", "ms/x")!);
|
|
102
|
+
const forgeMany = gitForge(REPO, cardRange([{ sha: first }, { sha: last }], "tk-x", "ms/x")!);
|
|
103
|
+
check(
|
|
104
|
+
"the forge link beside the pane still points at a real page for one commit",
|
|
105
|
+
forgeOne === `${REPO.url}/commit/${first}`,
|
|
106
|
+
String(forgeOne),
|
|
107
|
+
);
|
|
108
|
+
check(
|
|
109
|
+
"and at the same span for several",
|
|
110
|
+
forgeMany === `${REPO.url}/compare/${first}^...${last}`,
|
|
111
|
+
String(forgeMany),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
/* The route builder is the door's grammar and nothing here re-spells it. */
|
|
115
|
+
check(
|
|
116
|
+
"the pane's data-range IS gitRoute of the target the call site chose",
|
|
117
|
+
asked(single, now) === gitRoute(cardRange(single.commits, single.branch, single.milestone?.branch ?? "")!),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -190,7 +190,7 @@ export async function run(fixture: Fixture, check: Check, h: Harness): Promise<v
|
|
|
190
190
|
* on a `taskops serve` host), which must still be a readable pane and never a
|
|
191
191
|
* dead anchor. */
|
|
192
192
|
const filesChanged = renderToStaticMarkup(
|
|
193
|
-
<FilesChanged reader={undefined} repo={REPO}
|
|
193
|
+
<FilesChanged reader={undefined} repo={REPO} target={{ kind: "compare", base: "ms/x", head: "tk-a11111" }} />,
|
|
194
194
|
);
|
|
195
195
|
check(
|
|
196
196
|
"Files changed with no reader falls to the forge, not to nothing",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* pane scrolls on its own X axis (`overflow: auto` over `white-space: pre`) and
|
|
25
25
|
* every grid cell above it is `minmax(0, …)`. Nothing here may widen the drawer:
|
|
26
26
|
* a horizontal scrollbar on the page is the one failure mode this pane has. */
|
|
27
|
-
import { Ext, Numstat, cascade, useGitDiff, type DiffStep, type GitReader, type GitTarget, type Repo } from "../../links";
|
|
27
|
+
import { Ext, Numstat, cascade, gitRoute, useGitDiff, type DiffStep, type GitReader, type GitTarget, type Repo } from "../../links";
|
|
28
28
|
import { Fragment, useState } from "react";
|
|
29
29
|
import type { Counts } from "../../links";
|
|
30
30
|
import { split, type Hunk, type Side } from "./split";
|
|
@@ -441,12 +441,22 @@ const summaryBar: React.CSSProperties = {
|
|
|
441
441
|
borderBottom: "1px solid var(--hair)",
|
|
442
442
|
};
|
|
443
443
|
|
|
444
|
-
/** Files changed — the card AS A PULL REQUEST.
|
|
444
|
+
/** Files changed — the card AS A PULL REQUEST, over a range its CALLER chose.
|
|
445
445
|
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
446
|
+
* It takes a whole `GitTarget` and not a `base`/`head` pair, because the two
|
|
447
|
+
* callers do not ask the same question: the dossier asks for the range the
|
|
448
|
+
* board RECORDED (a commit, or `<first>^...<last>` — `links.tsx::cardRange`),
|
|
449
|
+
* and the Worktrees diff page asks about two live branches on disk. A pair of
|
|
450
|
+
* strings could only express the second, which is how the dossier's pane came
|
|
451
|
+
* to hold a base no host had.
|
|
452
|
+
*
|
|
453
|
+
* `data-range` is the route it asked for, on the wrapper. It is there so the
|
|
454
|
+
* RANGE can be asserted where it is chosen: the fetch is a `useEffect` and
|
|
455
|
+
* `react-dom/server` fires none, so without it a mutation at the dossier's
|
|
456
|
+
* call site renders identically and goes green — which is exactly what
|
|
457
|
+
* happened once already (`smoke/sections/card-diff-range.tsx` says so). It
|
|
458
|
+
* states what this pane is showing, which is also the first thing to read when
|
|
459
|
+
* a pane is empty and nobody knows what it asked for.
|
|
450
460
|
*
|
|
451
461
|
* The list is the FIRST step of the cascade in its own right: `stat` is the
|
|
452
462
|
* numstat vocabulary, so a reader who never expands a file still gets +/− per
|
|
@@ -454,15 +464,13 @@ const summaryBar: React.CSSProperties = {
|
|
|
454
464
|
export function FilesChanged({
|
|
455
465
|
reader,
|
|
456
466
|
repo,
|
|
457
|
-
|
|
458
|
-
head,
|
|
467
|
+
target,
|
|
459
468
|
summary = false,
|
|
460
469
|
view = DRAWER_VIEW,
|
|
461
470
|
}: {
|
|
462
471
|
reader: GitReader | null | undefined;
|
|
463
472
|
repo: Repo | null | undefined;
|
|
464
|
-
|
|
465
|
-
head: string;
|
|
473
|
+
target: GitTarget;
|
|
466
474
|
/** Draw the `N files changed +a −d` bar above the list.
|
|
467
475
|
*
|
|
468
476
|
* A PROP here rather than a computation at the call site, because the count
|
|
@@ -477,17 +485,18 @@ export function FilesChanged({
|
|
|
477
485
|
* the drawer's, so the dossier pane is byte-for-byte what it was. */
|
|
478
486
|
view?: PatchView;
|
|
479
487
|
}): React.JSX.Element {
|
|
480
|
-
const target: GitTarget = { kind: "compare", base, head };
|
|
481
488
|
const state = useGitDiff(reader, target, true);
|
|
482
489
|
return (
|
|
483
|
-
<
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
490
|
+
<div data-range={gitRoute(target)} style={{ minWidth: 0 }}>
|
|
491
|
+
<FileList
|
|
492
|
+
reader={reader}
|
|
493
|
+
repo={repo}
|
|
494
|
+
target={target}
|
|
495
|
+
step={cascade(repo, target, state)}
|
|
496
|
+
summary={summary}
|
|
497
|
+
view={view}
|
|
498
|
+
/>
|
|
499
|
+
</div>
|
|
491
500
|
);
|
|
492
501
|
}
|
|
493
502
|
|
|
@@ -516,15 +525,19 @@ export function FileList({
|
|
|
516
525
|
summary?: boolean;
|
|
517
526
|
view?: PatchView;
|
|
518
527
|
}): React.JSX.Element {
|
|
519
|
-
const { base, head } = target.kind === "compare" ? target : { base: "", head: target.ref };
|
|
520
528
|
if (step.step !== "patch") return <DiffPane step={step} view={view} />;
|
|
521
529
|
|
|
522
530
|
const stat: Counts = step.diff.stat;
|
|
523
531
|
const paths = Object.keys(stat).sort();
|
|
524
532
|
if (paths.length === 0) {
|
|
533
|
+
/* Named in the grammar of the range that was actually asked: a compare has
|
|
534
|
+
two ends and a commit has one, and "between <sha> and " was the sentence
|
|
535
|
+
a single-commit target used to produce. */
|
|
525
536
|
return (
|
|
526
537
|
<div data-testid="changed-none" style={note}>
|
|
527
|
-
|
|
538
|
+
{target.kind === "compare"
|
|
539
|
+
? `no files differ between ${target.base} and ${target.head}`
|
|
540
|
+
: `no files changed in ${target.ref}`}
|
|
528
541
|
</div>
|
|
529
542
|
);
|
|
530
543
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* to agents. That is the hole `<Criteria>` closes, and the reason it is a section
|
|
14
14
|
* of its own rather than a paragraph folded into the spec. */
|
|
15
15
|
import { shortActor } from "../../format";
|
|
16
|
-
import { Ext, Numstat,
|
|
16
|
+
import { Ext, Numstat, cardRange, commitUrl, type GitReader, type Repo } from "../../links";
|
|
17
17
|
import { TONE_FG } from "../board/CardTile";
|
|
18
18
|
import { Markdown } from "../shared/Markdown";
|
|
19
19
|
import { CommitPatch, FilesChanged } from "./Patch";
|
|
@@ -114,6 +114,7 @@ export function Body({
|
|
|
114
114
|
}): React.JSX.Element {
|
|
115
115
|
const { card } = dossier;
|
|
116
116
|
const stood = dossier.standing;
|
|
117
|
+
const range = cardRange(dossier.commits, dossier.branch, dossier.milestone?.branch ?? "");
|
|
117
118
|
return (
|
|
118
119
|
<>
|
|
119
120
|
{stood && stood.verdict === "changes" ? (
|
|
@@ -287,24 +288,21 @@ export function Body({
|
|
|
287
288
|
) : null}
|
|
288
289
|
|
|
289
290
|
{/* The card AS A PULL REQUEST, read from the host's own git (or this
|
|
290
|
-
clone) instead of the forge — the
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
291
|
+
clone) instead of the forge — and over the range the BOARD recorded,
|
|
292
|
+
both ends of it. `links.tsx::cardRange` is the whole decision and
|
|
293
|
+
argues it: a lone recorded commit goes through the commit door, which
|
|
294
|
+
diffs it against its first parent; several become
|
|
295
|
+
`<first>^...<last>`, whose base is the oldest commit's PARENT so that
|
|
296
|
+
commit's own changes are in the pane rather than one off the edge of
|
|
297
|
+
it. Neither end is a branch name, which is what makes the pane render
|
|
298
|
+
for a card whose `tk-*` and `ms/*` branches were pruned before this
|
|
299
|
+
host became the board's remote — the reported failure. This used to be
|
|
300
|
+
described as "the same two branches the `compare ↗` link above is
|
|
301
|
+
built from", and that link is gone: the Worktree block opens the tree
|
|
302
|
+
in this dashboard instead. No range to ask for, no section. */}
|
|
303
|
+
{range !== null ? (
|
|
301
304
|
<Section title="Files changed">
|
|
302
|
-
<FilesChanged
|
|
303
|
-
reader={reader}
|
|
304
|
-
repo={repo}
|
|
305
|
-
base={dossier.milestone.branch}
|
|
306
|
-
head={cardHead(dossier.commits, dossier.branch)}
|
|
307
|
-
/>
|
|
305
|
+
<FilesChanged reader={reader} repo={repo} target={range} />
|
|
308
306
|
</Section>
|
|
309
307
|
) : null}
|
|
310
308
|
|