feat: add precise zone-aware board placement by tzercin · Pull Request #3099 · preset-io/agor · GitHub
Skip to content

feat: add precise zone-aware board placement - #3099

Open
tzercin wants to merge 4 commits into
preset-io:mainfrom
tzercin:feat/zone-anchored-placement
Open

tzercin wants to merge 4 commits into
preset-io:mainfrom
tzercin:feat/zone-anchored-placement

Conversation

@tzercin

@tzercin tzercin commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add a shared zone-relative placement resolver and canonical nine-anchor specification. Explicit coordinates clamp to usable bounds; anchors account for branch/card dimensions and adaptive padding; omitted positions preserve random placement.
  • Accept position on branch create/set-zone and card create/move. Correct the misleading “zone center” description and document coordinates, anchors, and trigger behavior.
  • Add agor_boards_set_position for an existing branch/card placement, using its board-object ID. Pinned entities use zone-relative coordinates/anchors; unpinned entities use absolute canvas coordinates.

Repositioning design / authorization

Use the existing authenticated board-objects.get → board-objects.patch path rather than extending set_zone: it works for both entity types, preserves zone membership, retains the existing board capability/RBAC and tenant hooks, and cannot execute zone prompt templates. set_zone retains its existing trigger semantics, including always_new. No schema or migration changes.

Board objects and their parent boards/entities remain tenant-owned. A real HTTP MCP test uses the production service hooks and a scope-required database proxy against PostgreSQL as a verified NOSUPERUSER/NOBYPASSRLS role. Both direct and facade calls deny a foreign-tenant admin and a same-tenant non-editor without modifying the target; the owner can reposition branches and cards in an always_new zone without triggering work. The same-tenant caller has board Viewer access, so card denial also exercises the mutation capability gate.

Validation

All final checks passed; 195 tests passed:

  • Core: pnpm --filter @agor/core exec vitest run src/utils/board-placement.test.ts — 23 passed.
  • Daemon: pnpm --filter @agor/daemon exec vitest run src/mcp/tools/boards.test.ts src/mcp/tools/branches.test.ts src/mcp/tools/branches.tenant-scope.test.ts src/mcp/tools/branches.realtime.test.ts src/mcp/tools/cards.test.ts src/services/cards.test.ts src/register-hooks.board-entities.test.ts src/register-hooks.board-mcp.test.ts src/services/repos.test.ts — 171 passed, 9 suites.
  • PostgreSQL: AGOR_DB_DIALECT=postgresql AGOR_TEST_POSTGRES_URL=<disposable database> pnpm --filter @agor/daemon exec vitest run src/mcp/tools/board-placement.postgres.test.ts — 1 passed, actual PostgreSQL/RLS (not skipped).
  • pnpm --filter @agor/core --filter @agor/daemon exec tsc --noEmit --customConditions source --rootDir ../.. — exit 0. Source resolution avoids triggering Turbo's dependency builds in the fresh worktree.
  • Additional standalone typecheck of all five touched daemon test files using a temporary config extending daemon tsconfig with tests included — exit 0. Tightened existing fixture typings and entered an explicit tenant scope for the card service fixture.
  • Biome check of all 13 touched TypeScript files, Prettier check of the guide, pnpm check:multitenancy-boundaries, and Git whitespace check — passed.

No pnpm build was run. A source-mode local preview was started separately for manual development; its deployment-local configuration is not part of this PR.

UI follow-up

  • Add Edit Branch → General → Work Context → Move on board…, immediately below the Board selector.
  • The on-demand Ant Design dialog offers a zone, all nine anchors, or explicit X/Y, with a clamped result preview. Choosing No zone (canvas) converts the current position to absolute coordinates and allows unpinning.
  • Move branch is an explicit placement-only action, separate from the branch metadata Save Changes flow, and does not dispatch zone prompts. Pending board changes must be saved first.
  • Read effective board-edit capability and use the same authenticated board-objects PATCH as canvas/MCP. Do not infer board permission from branch metadata-edit permission. Guard stale placements, unavailable zones, failures, and identity changes while awaiting reads.
  • Update the branches guide. No new backend route, migration, permission bypass, or tenant selector was added.

UI follow-up validation

  • UI: cd apps/agor-ui && pnpm exec vitest run src/components/BranchModal/BranchPlacementModal.test.tsx src/components/BranchModal/BranchModal.placement.test.tsx src/components/BranchModal/BranchModal.test.tsx src/components/BranchModal/useBranchModalForm.test.tsx src/components/SessionCanvas/SessionCanvas.placement-reconciliation.test.tsx — 116 passed, 5 suites.
  • Daemon: cd apps/agor-daemon && pnpm exec vitest run src/register-hooks.board-entities.test.ts src/register-hooks.board-mcp.test.ts src/services/board-objects.test.ts — 8 passed, 3 suites.
  • Core placement resolver suite — 23 passed.
  • UI source typecheck: cd apps/agor-ui && pnpm exec tsc -p tsconfig.app.json --customConditions source --erasableSyntaxOnly false --noEmit — exit 0. The source-mode override is necessary because existing core source uses enums/parameter properties; it avoids dependency builds. The unmodified erasable-only setting rejects those existing core constructs.
  • Both new UI test files separately typechecked with a temporary config extending the UI config and the same source-mode flags — exit 0.
  • Biome CI on all four new/changed UI files, guide Prettier check, and git whitespace check — passed.
  • Live preview: UI modules HTTP 200; demo board placements and effective board-edit capability load successfully. Automated visual verification remains unavailable because Chromium system libraries are absent in the agent environment.

The new UI tests cover Work Context placement, no-board disabling, coordinates/clamping, anchor moves, unpinning, board access denial, rejected patches, stale placement, empty input, and cancelling a pending write after an authority change. The existing PostgreSQL cross-tenant/RLS evidence above covers the unchanged backend mutation boundary.

Teammate → Branches entry point

  • Add a per-branch Move on board… icon beside the locator in the Teammate panel’s Branches list. Reuse the existing placement dialog rather than introducing another mutation path.
  • Gate the action on the panel’s effective board-edit capability; disable archived/deleting branches. Stop click propagation so opening placement neither pans the canvas nor closes the panel.
  • Keep one on-demand dialog per list, preserve its close animation, and clear the selected branch when board access is lost. Existing board-object realtime events refresh zone labels.
  • Tenant-owned placement still uses the same authenticated, tenant-scoped service boundary and the shared dialog’s authority-switch guard; no new tenant selector or backend route.

Validation for this entry point:

  • pnpm --filter agor-ui exec vitest run src/components/BranchListDrawer/BoardBranchList.placement.test.tsx src/components/BranchListDrawer/BoardBranchList.test.tsx src/components/BranchModal/BranchPlacementModal.test.tsx src/components/BoardTeammatePanel/BoardTeammatePanel.create.test.tsx — 34 tests passed, 4 suites.
  • UI source typecheck with --customConditions source --erasableSyntaxOnly false --noEmit — exit 0; new test separately typechecked against the UI config with the same flags — exit 0.
  • Biome CI, guide formatting, and git whitespace check passed. Preview serves the updated branch-list module (HTTP 200). No build was run.

@tzercin

tzercin commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

@tzercin

tzercin commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

@kasiazjc

Screen.Recording.2026-10-14.at.23.55.52.mp4

@mistercrunch

Copy link
Copy Markdown
Member

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants