Summary
Makes the Toolkit Policies page (/settings/policies, org-admin) a more visual, centralized
management surface. An admin can now create, configure, assign, and manage a policy from one
place instead of navigating to the Teams section to actually wire things up.
A key enabler: most of the infrastructure already existed — the team-assignment tRPC procedures,
the read/write/destructive action-sensitivity classifier, ToolkitLogo, and the app-catalog card
grid. This PR mostly surfaces and adapts those onto the policy page rather than building from
scratch.
What's new
- 1 — "+" icon on New Policy. Lucide Plus inside the header CTA for discoverability.
- 2 — Policies as visual cards. Replaced the single-column list with a responsive card grid
(app-catalog-style); each card shows an overlapping toolkit-logo stack (up to 4, then "+N"), the
policy name, a rule summary, and the assignment count. Adds a loading skeleton and an EmptyState
CTA.
- 3 — Toolkit thumbnails. Reuses ToolkitLogo (logo with initials fallback); the list page now
loads the catalog to map toolkit slug → logo.
- 4 — Assign teams from the policy page. New ManageAssignedTeams: a TeamMultiSelect dialog (keyed
by team id) with add/remove diffing, active/dormant lists, dormant-team warnings, and an inline
"Enable shared policies" action.
- 5 — Permission presets. Full access / Non-destructive / Read-only, derived from best-effort
action sensitivity.
- 6 — Teams-style action config. A per-toolkit "Configure actions" dialog mirroring the Teams
Tool Config: preset buttons, read/write/destructive filter chips, an open-world filter, and a
searchable virtualized action list.
How it works (key decisions)
- Rule model: org policies are allow-list only ({mode:"all_actions"} | {mode:"selective",
allowedActions}). Presets expand to concrete rules (not a stored mode): full_access→all_actions,
read_only→selective(read), non_destructive→selective(universe − destructive). Pure adapter lives
in packages/types/src/policy-presets.ts.
- Classification reuse: added an org-scoped toolkitPolicies.getToolkitClassification
(orgReadProcedure) that reuses the existing tenant-scoped fetchToolkitClassification; surfaced
the already-computed sensitivity field that PolicyToolkitActionSchema was stripping.
- Casing: allowedActions are stored uppercase to match the runtime/preview action universe;
classification is framed as best-effort in the UI.
- Stale slugs: the action dialog carries forward selected-but-missing action slugs so a removed
action stays visible/removable.
Scope changed
26 files, +2567 / −288. New: the preset adapter + tests, the org classification procedure,
PolicyCard/skeleton, the action dialog (+ hook + helpers), ManageAssignedTeams (+ helper).
Replaced the read-only AssignedTeamsSection (deleted) and the inline policy action editor.
Deferred (follow-ups)
- Cross-toolkit bulk-preset apply.
- Quick-assign directly from a policy card.
Testing
- bunx turbo check-types and bunx turbo lint — green across all packages.
- bun run ci:preflight (clean-env) — green (lint 9/9, check-types 10/10).
- New: policy-presets, policy-card, policy-action-dialog.helpers, policy-action-rule-dialog,
manage-assigned-teams(.helpers) tests + org-isolation tests for the new procedure. Existing
policy-editor.helpers.test.ts and server toolkit-policies.test.ts kept green.
A self-review pass (/code-review) hardened partial-save cache reconciliation, stale-slug
visibility/counts, per-row Enable state, the dormant-warning scope, and empty-preset guards.