refactor,feat(dashboard): allow adding permissions to specific project#6226
refactor,feat(dashboard): allow adding permissions to specific project#6226ogzhanolguncu wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
ℹ️ Minor suggestions only — the ADT refactor is clean and the regex widening is justified, but a couple of rough edges below.
Reviewed changes — adds project-scoped permission rows to the root-key dialog and refactors the dashboard permission UI from a type + apiId pair to a PermissionScope discriminated union (workspace | api | project). Also widens the RBAC ID regex in web/internal/rbac/src/permissions.ts from Crockford base32 to full [0-9A-Za-z], fixing rejections on backend-generated proj_ IDs.
PermissionScopeADT indialog/permissions.ts— discriminated union plusgetScopedPermissions(scope)replaces the workspace-sentinel string and thetype === "workspace" ? ... : apiIdbranching scattered across the badge list, content list, andusePermissions.projectPermissions(projectId)factory — mirrorsapiPermissions, exposes the three actions defined inprojectActions(create_deployment,read_deployment,generate_upload_url).usePermissionSheetrefactor — extractsrebuildScopedPermsandcollectPermissions(list, build, skipId)so the "preserve other scopes' selections" logic is shared betweenhandleApiPermissionChangeand the newhandleProjectPermissionChange.useRootKeyDialog— addstrpc.deploy.project.listquery, exposesallProjects/projectsLoading;canUpdatenow waits for both API + project lists in create mode.PermissionContentList/PermissionBadgeList— both takescopedirectly;permission-list.tsxuses@unkey/matchfor scope-header dispatch.buildIdSchemaregex — relaxed from Crockford base32 (no0/O/I/l) to[0-9A-Za-z]{8,32}. Justified: Go'spkg/uid/new.gouses base62, soctrl.project.createProjectemits IDs the old TS regex would reject.
ℹ️ Underlying TS/Go ID-alphabet divergence
The regex widening here is the right fix for the symptom, but the root cause is that the two ID generators in this repo disagree on alphabet.
web/internal/id/src/generate.ts:3— TSnewIduses Crockford base32 (123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz), explicitly excluding0/O/I/l.pkg/uid/new.go:8— Gouid.Newuses full base62 (abc..xyzABC..XYZ0..9), so anyproj_(or future resource) created via the ctrl service can contain the confusable characters Crockford skips.
Today the only resource hitting unkeyPermissionValidation from the Go side is proj_, so widening the regex is sufficient. Worth a follow-up issue to unify the alphabets so this doesn't bite the next resource the ctrl service mints.
Technical details
# Underlying TS/Go ID-alphabet divergence
## Affected sites
- `pkg/uid/new.go:8` — `defaultAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"` (base62)
- `web/internal/id/src/generate.ts:3` — `customAlphabet("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")` (Crockford base32)
- `web/internal/rbac/src/permissions.ts:17` — regex now `[0-9A-Za-z]{8,32}` to accept both
## Required outcome
- Decide on one canonical alphabet for prefixed IDs across the codebase, or document why the two paths diverge.
## Suggested approach (optional)
- Easiest: switch Go's `uid.New` to Crockford to match TS, then revert the regex to the stricter Crockford allowlist. Crockford was presumably chosen on the TS side to avoid human-confusable characters in dashboard-visible IDs — that rationale applies equally to backend-minted IDs.
- Alternative: switch TS to base62 to match Go. Loses confusable-character protection but unifies the system.
## Open questions for the human
- Was Crockford a deliberate choice for dashboard-visible IDs (e.g. copy/paste UX), or incidental?Claude Opus | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — addresses the two open suggestions from the initial pullfrog review; no new behavior.
WORKSPACE_SCOPEdedup — extracted frompermission-sheet.tsxandroot-key-dialog.tsxinto a single exported const indialog/permissions.ts. Both call sites now import it.- Gated
deploy.project.listquery —useRootKeyDialogtakes a newisOpen: booleanprop and passes{ enabled: isOpen }to the tRPC query, so non-deploy workspaces (and unopened dialogs) no longer pay the round-trip on mount.
Claude Opus | 𝕏

Fixes #6014.
This PR allows us to configure permissions for specific projects instead of selecting them workspace level. Also refactors way we handle permission logic in the UI.
Note
Adjusted RBAC ID regex (
web/internal/rbac/src/permissions.ts) ([0-9A-Za-z]). Some project IDs containing0/O/I/lwere rejected byunkeyPermissionValidation, surfacing as "You need to add at least one permission" on root key create.Screen.Recording.2026-05-21.at.16.19.19.mov
How to test