Skip to content

feat: Root key creation redesign.#3779

Merged
perkinsjr merged 41 commits intomainfrom
root-key-redisign
Aug 20, 2025
Merged

feat: Root key creation redesign.#3779
perkinsjr merged 41 commits intomainfrom
root-key-redisign

Conversation

@perkinsjr
Copy link
Member

@perkinsjr perkinsjr commented Aug 12, 2025

What does this PR do?

This adds the new modal version of the creation and editing of a root key.

Fixes # (issue)

The issues are already closed but closes out Root key redesign project

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Create a new root key
  • Create a new root key and add permissions
  • Edit a root key and change it's name
  • Edit a root key and change it's permissions

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Full Root Key create/edit flow: dialog, success modal (copy/mask), create button, editable table action, searchable permission sheet with workspace & per-API editors, collapsible categories, badges, toggles, highlighted search, load-more for APIs, and comprehensive permission management utilities/hooks.
  • Backend/API

    • Update endpoint now accepts and persists root key permissions.
  • Style

    • Checkbox indeterminate visuals with new minus icon; sheet overlay variants, customizable/optional close icon.
  • Documentation

    • Added comprehensive README for refactored Root Key components.

Summary by CodeRabbit

  • New Features

    • Full Root Key flow: create/edit dialog, permission sheet (workspace + per‑API), success modal, inline table edit for editing keys.
  • Improvements

    • Search with highlighted results, collapsible categories, permission badges, tri‑state checkbox visuals, customizable sheet overlay/close, new input "ghost" style, minor icon additions, performance/memoization improvements.
  • Documentation

    • Added comprehensive README for refactored Root Key components.
  • Removed / Breaking Changes

    • Legacy Root Key pages/components and older per‑permission add/remove endpoints replaced by a single permissions update mutation.

MichaelUnkey and others added 8 commits July 21, 2025 14:22
* Dialog created

* small changes
…3654)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect
…3663)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* button size and margin

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* functioning again

* style change

* more tweaks

* Fix all the fucking things

* remove close button

* fmt

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>
* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* functioning again

* style change

* more tweaks

* Fix all the fucking things

* remove close button

* fmt

* dialog and confirm added

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>
* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* chore: deprecate v1 endpoints (#3680)

* fix: openapi

* chore: deprecate v1 endpoints

* fix: vault credentials and chproxy config (#3681)

* fix: openapi

* fix: vault credentials and chproxy config

* fix: rename flag accessor too

* fix: linter issues

* fix: some more v2 api changes (#3677)

* remove namespaceID

* actually use limit and cursor

* filter out delted overrides

* fix error messages list endpoints

* fix more error messages

* ensure identity create handles like permission/role create

* fix regex for roles

* fix regex for roles

* fix list keys cursor

* fix: uppercase common files (#3683)

* name files uppercase

* name files uppercase

* [autofix.ci] apply automated fixes

* name files uppercase

* name files uppercase

---------

Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: conflicting casing (#3689)

* fix the openapi spec again (#3692)

* dont trace chproxy endpoints (#3691)

* fix: log verifications to the owning workspace (#3693)

* functioning again

* style change

* more tweaks

* fix: validate s3 config (#3694)

* Fix all the fucking things

* remove close button

* fmt

* fix: speakeasy ignore directive is ignored if it's a string (#3699)

* fix: upsert permissions with slug or name colission (#3696)

* fix: upsert permissions with slug or name colission

* chore: also remove index

* fix permission test and remove unnnecessary test

---------

Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>

* docs: migration (#3678)

* fix: openapi

* docs: migration from v1 to v2

* fix: remove binaries

* [autofix.ci] apply automated fixes

* docs: add james' feedback

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: api list keys zod errors (#3702)

* fix api zod errors

* [autofix.ci] apply automated fixes

* make array handling uniform

* make array handling uniform

* fix rabbi comment

* fix: permission array for roles

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* ci: don't build for windows and freebsd (#3700)

* docs: errors (#3703)

* chore: move sdks to unkeyed/sdks (#3701)

* fix: omitting array vs null (#3704)

* fix omitting array vs null

* [autofix.ci] apply automated fixes

* fix flakey test

* fix flakey test

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: panics not being catched (#3706)

* fix panics not being catched

* feat: add counter to track panics

Signed-off-by: Ian Meyer <k@imeyer.io>

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: Ian Meyer <k@imeyer.io>

* docs: use `CodeGroup` in hono/nextjs TS libraries (#3708)

* Update hono.mdx

* Update nextjs.mdx

* ci: remove outdated steps and flows (#3709)

* docs: update sdks (#3712)

* docs: update sdks

* Update nextjs.mdx

* [autofix.ci] apply automated fixes

* fix: rabbit feedback

* Update nextjs.mdx

* fix: root key is required

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* dialog and confirm added

* feat(deployment beta): projects UI for Unkey Deploy (#3662)

* projects and branches

* wip

* wip

* spec differ wip

* fix some docker, add some trpc, integrate diff viewer

* change version to deployments, add feature flag, update go schema

* update versions page

* fix null condition

* delete old router, fix null assertion

* fmt

* fmt

* fmt again

* apply auth and feature flagging access to projects, remove versions

* yolo

* stable yolo

* stable yolo

* style: fmt

* fix: hardcode time, so it doesn't fail on the first of a month

* [autofix.ci] apply automated fixes

---------

Co-authored-by: chronark <dev@chronark.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* chore: add missingd delete endpoint for v2 (#3711)

* only log key at the end (#3716)

* fix: allow wildcard and colon in permissions query (#3717)

* remove regex for permissions

* allow for asterix and colon in permissions

* fix: update identity by identity key instead of externalId and fix wrong body for permission and role (#3713)

* docs and remove externalId from keyResponse

* fix updateIdentity to take in an identity parameter instead of an externalId

* fix get role/permission

* Update go/apps/api/openapi/spec/common/Permission.yaml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update openapi-generated.yaml

* fix comment

---------

Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* chore: openapi references (#3723)

* feat: add paginated tRPC endpoint for projects (#3697)

* feat: add new endpoint for deploy projects

* chore: replace file path

* [autofix.ci] apply automated fixes

* feat: add missing endpoint

* fix: trpc path

* fix: add feature flag

* chore: remove optin

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: region not showing and wrong rl id (#3722)

* fix: region not showing and wrong rl id

* pass region down

* perf: bad get key performance (#3724)

* perf: make getKey 2 seperate queries so mysql chooses correct idx

* fix query

* fix query name

* docs: verify identities endpoints (#3727)

* chore: docs (#3728)

* chore: fixup migration guide

* adjust more

* adjust more

* adjust more

* rabbit comments

* Update index.mdx

* working updates

* re factor for clarity

* only update if diff than existing

* [autofix.ci] apply automated fixes

* re name create-root-key to root-key folder

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>
Co-authored-by: Oğuzhan Olguncu <21091016+ogzhanolguncu@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>
Co-authored-by: Ian Meyer <k@imeyer.io>
Co-authored-by: JA Castro <51177379+ubinatus@users.noreply.github.com>
Co-authored-by: Meg Stepp <mcstepp@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@vercel
Copy link

vercel bot commented Aug 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
dashboard Ignored Ignored Preview Aug 20, 2025 3:16pm
engineering Ignored Ignored Preview Aug 20, 2025 3:16pm

@github-actions
Copy link
Contributor

github-actions bot commented Aug 12, 2025

Thank you for following the naming conventions for pull request titles! 🙏

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 12, 2025

📝 Walkthrough

Walkthrough

Refactors root-key UI and permission system: adds typed permission catalog, reducer, hooks, and many client components (dialog, sheet, list, badges, toggles, search); centralizes constants/messages; replaces add/remove RBAC mutations with updateRootKeyPermissions; updates table/navigation to dialog flows; enhances UI primitives and removes legacy per-key pages/components.

Changes

Cohort / File(s) Summary
Docs
apps/dashboard/app/(app)/settings/root-keys/components/root-key/README.md
New README documenting refactored root-key architecture, hooks, utils, components, usage, and migration notes.
Root-key components
apps/.../root-keys/components/root-key/components/*
apps/.../root-keys/components/root-key/create-rootkey-button.tsx, .../root-key-dialog.tsx, .../root-key-success.tsx, .../constants.ts
Added many client components: ExpandableCategory, HighlightedText, PermissionBadgeList, PermissionContentList, PermissionSheet, PermissionToggle, SearchInput (and SEARCH_MODES), SearchPermissions, CreateRootKeyButton, RootKeyDialog, RootKeySuccess, and constants ROOT_KEY_CONSTANTS / ROOT_KEY_MESSAGES.
Root-key hooks
apps/.../root-keys/components/root-key/hooks/*
New hooks: usePermissions, usePermissionSheet, useRootKeyDialog, useRootKeySuccess handling permission state, sheet orchestration, dialog flow, and success confirmation.
Permissions model & utils
apps/.../root-keys/components/root-key/utils/permissions.ts, .../permissions.ts
New permission catalog (workspacePermissions, apiPermissions), types (PermissionItem/Category/List), filter/checked-state utilities, computeCheckedStates, permissionReducer, hasPermissionResults.
Create/edit integration & table/nav
apps/.../table/.../root-keys-table-action.popover.constants.tsx, apps/.../components/table/root-keys-list.tsx, apps/.../root-keys/navigation.tsx
Table actions accept onEditKey callback; root-keys list now wires edit dialog (editingKey → RootKeyDialog); navigation now uses CreateRootKeyButton instead of inline create link.
TRPC router & mutation
apps/dashboard/lib/trpc/routers/index.ts, apps/.../updateRootKeyPermissions.ts
Added updateRootKeyPermissions mutation and wired permissions into rootKey.update; removed legacy addPermissionToRootKey and removePermissionFromRootKey mutations.
UI primitives & icons
apps/dashboard/components/ui/sheet.tsx, internal/icons/src/icons/minus.tsx, internal/icons/src/index.ts, internal/ui/src/components/form/checkbox.tsx, internal/ui/src/index.ts
SheetContent gains overlay variants, closeIcon and disableClose props; added Minus icon and re-export; Checkbox visuals/types extended to support indeterminate state and use Minus; re-exported search-icon.
Input styling
internal/ui/src/components/form/input.tsx
Added ghost variant to Input and wrapper variants (styling + types).
Utility import change
apps/.../root-keys/components/controls/components/root-keys-filters/index.tsx
Replaced cn import source from @unkey/ui/src/lib/utils to @/lib/utils.
Removed legacy pages & components
apps/.../root-keys/new/*, apps/.../root-keys/[keyId]/*, TRPC rbac add/remove
Removed legacy New page and client/navigation, many per-key components/pages (Api, Workspace, PermissionManagerCard, permission_toggle, add-permission dialog, legacy/selector/history/access-table, update-root-key-name), and deleted old RBAC add/remove TRPC mutations.
Small icon/prop tweaks
internal/icons/src/icons/check.tsx, internal/icons/src/props.ts
Added focusable?: boolean to IconProps and forwarded focusable to Check SVG.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Table as RootKeys Table
  participant Dialog as RootKeyDialog
  participant Hook as useRootKeyDialog
  participant TRPC as TRPC Router
  participant DB as Database

  User->>Table: click "Edit root key..."
  Table->>Dialog: open(existingKey) via onEditKey
  Dialog->>Hook: init (name, permissions, fetch APIs)
  User->>Dialog: modify name/permissions
  User->>Dialog: click Save
  Dialog->>Hook: handleCreateKey / handleUpdate
  alt edit mode
    Hook->>TRPC: rootKey.update { name?, permissions? }
    TRPC->>DB: update key & permissions
    DB-->>TRPC: OK
    TRPC-->>Hook: success
    Hook-->>Dialog: close
  else create mode
    Hook->>TRPC: rootKey.create { name, permissions }
    TRPC->>DB: insert key + permissions
    DB-->>TRPC: OK (key, secret)
    TRPC-->>Hook: success
    Hook-->>Dialog: show RootKeySuccess
  end
Loading
sequenceDiagram
  participant User
  participant Sheet as PermissionSheet
  participant Hook as usePermissionSheet
  participant Util as permissions utils

  User->>Sheet: type search
  Sheet->>Hook: handleSearchChange(value)
  Hook->>Sheet: set isProcessing, searchValue
  Sheet->>Util: filterPermissionList(searchValue)
  Util-->>Sheet: filtered lists
  User->>Sheet: toggle root/category/permission
  Sheet->>Hook: handleApiPermissionChange / handleWorkspacePermissionChange
  Hook->>Caller: onChange(mergedPermissions)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Feature, Documentation, 🕹️ oss.gg, :joystick: 300 points

Suggested reviewers

  • perkinsjr
  • ogzhanolguncu

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch root-key-redisign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@changeset-bot
Copy link

changeset-bot bot commented Aug 12, 2025

⚠️ No Changeset found

Latest commit: 668cc5a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 42

🔭 Outside diff range comments (2)
apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (1)

32-41: Prevent row selection when clicking the actions placeholder

While the dynamic import is loading, the placeholder button doesn’t stop event propagation. Clicking it will bubble to the row and trigger row selection or other row handlers, which is inconsistent with the real component behavior.

Apply this diff to stop propagation on the placeholder trigger:

       loading: () => (
         <button
           type="button"
+          onClick={(e) => e.stopPropagation()}
           className={cn(
             "group-data-[state=open]:bg-gray-6 group-hover:bg-gray-6 group size-5 p-0 rounded m-0 items-center flex justify-center",
             "border border-gray-6 group-hover:border-gray-8 ring-2 ring-transparent focus-visible:ring-gray-7 focus-visible:border-gray-7",
           )}
         >
apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx (1)

21-31: Disable the “Edit” action when no handler is provided

If onEditKey is undefined, the item currently does nothing. Disable it to avoid a dead control, or provide a fallback navigation.

Apply this diff:

     {
       id: "edit-root-key",
       label: "Edit root key...",
       icon: <PenWriting3 size="md-regular" />,
       onClick: () => {
         onEditKey?.(rootKey);
       },
+      disabled: !onEditKey,
       divider: true,
     },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f393622 and 61bc4eb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/README.md (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/highlighted-text.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-input.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permissions.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/utils/permissions.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (7 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/navigation.tsx (2 hunks)
  • apps/dashboard/components/ui/sheet.tsx (3 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts (1 hunks)
  • internal/icons/src/icons/minus.tsx (1 hunks)
  • internal/icons/src/index.ts (1 hunks)
  • internal/ui/src/components/form/checkbox.tsx (11 hunks)
  • internal/ui/src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • internal/icons/src/index.ts
  • internal/icons/src/icons/minus.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • internal/ui/src/index.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/navigation.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/utils/permissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-input.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/highlighted-text.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx
  • internal/ui/src/components/form/checkbox.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
  • apps/dashboard/components/ui/sheet.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • internal/icons/src/index.ts
  • internal/icons/src/icons/minus.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • internal/ui/src/index.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/navigation.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/utils/permissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-input.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/highlighted-text.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx
  • internal/ui/src/components/form/checkbox.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
  • apps/dashboard/components/ui/sheet.tsx
**/*.{env,js,ts,go}

📄 CodeRabbit Inference Engine (CLAUDE.md)

All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Files:

  • internal/icons/src/index.ts
  • internal/ui/src/index.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/utils/permissions.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts
🧠 Learnings (1)
📚 Learning: 2024-12-03T14:17:08.016Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/app/(app)/logs/logs-page.tsx:77-83
Timestamp: 2024-12-03T14:17:08.016Z
Learning: The `<LogsTable />` component already implements virtualization to handle large datasets efficiently.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
🧬 Code Graph Analysis (5)
internal/icons/src/icons/minus.tsx (1)
internal/icons/src/props.ts (2)
  • IconProps (30-35)
  • sizeMap (7-28)
apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx (3)
apps/dashboard/lib/trpc/routers/settings/root-keys/query.ts (1)
  • RootKey (35-35)
apps/dashboard/components/logs/table-action.popover.tsx (2)
  • TableActionPopover (29-148)
  • MenuItem (12-22)
internal/icons/src/icons/pen-writing-3.tsx (1)
  • PenWriting3 (15-45)
internal/ui/src/components/form/checkbox.tsx (2)
internal/icons/src/icons/check.tsx (1)
  • Check (15-38)
internal/icons/src/icons/minus.tsx (1)
  • Minus (15-38)
apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (5)
apps/dashboard/lib/trpc/routers/settings/root-keys/query.ts (1)
  • RootKey (35-35)
apps/dashboard/app/(app)/settings/root-keys/components/table/utils/get-row-class.ts (1)
  • getRowClassName (26-38)
apps/dashboard/app/(app)/settings/root-keys/components/table/components/skeletons.tsx (6)
  • RootKeyColumnSkeleton (4-13)
  • KeyColumnSkeleton (20-25)
  • CreatedAtColumnSkeleton (15-19)
  • PermissionsColumnSkeleton (27-34)
  • LastUpdatedColumnSkeleton (36-41)
  • ActionColumnSkeleton (43-53)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/components/virtual-table/index.tsx (1)
  • VirtualTable (38-404)
apps/dashboard/components/ui/sheet.tsx (1)
internal/icons/src/icons/xmark.tsx (1)
  • XMark (15-51)
🪛 LanguageTool
apps/dashboard/app/(app)/settings/root-keys/components/root-key/README.md

[grammar] ~1-~1: Use correct spacing
Context: # Root Key Components This directory contains the refactored r...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~3-~3: Use correct spacing
Context: ...key management components for the Unkey dashboard. ## Architecture Overview The components ha...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~5-~5: Use correct spacing
Context: ...r the Unkey dashboard. ## Architecture Overview The components have been refactored to f...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~7-~7: Use correct spacing
Context: ... to follow a clean architecture pattern with: - Custom Hooks: Business logic extracted...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~12-~12: Use correct spacing
Context: ...own into focused, single-responsibility pieces ## Directory Structure ``` root-key/ ├── c...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~14-~14: Use correct spacing
Context: ...gle-responsibility pieces ## Directory Structure root-key/ ├── components/ # UI components │ ├── expandable-category.tsx │ ├── highlighted-text.tsx │ ├── permission-badge-list.tsx │ ├── permission-list.tsx │ ├── permission-sheet.tsx │ ├── permission-toggle.tsx │ ├── search-input.tsx │ └── search-permissions.tsx ├── hooks/ # Custom hooks for business logic │ ├── use-permissions.ts │ ├── use-permission-sheet.ts │ ├── use-root-key-dialog.ts │ └── use-root-key-success.ts ├── utils/ # Utility functions │ └── permissions.ts ├── constants.ts # Shared constants and messages ├── create-rootkey-button.tsx ├── root-key-dialog.tsx ├── root-key-success.tsx └── README.md ## Key Improvements ### 1. Custom Hooks - ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~41-~41: Use correct spacing
Context: ...y-success.tsx └── README.md ``` ## Key Improvements ### 1. Custom Hooks - usePermissions: ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~43-~43: There might be a mistake here.
Context: ...``` ## Key Improvements ### 1. Custom Hooks - usePermissions: Manages permission state and logic - *...

(QB_NEW_EN_OTHER)


[grammar] ~47-~47: Use correct spacing
Context: ...ss**: Handles success dialog state and navigation ### 2. Utility Functions - **permissions.ts...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~49-~49: There might be a mistake here.
Context: ...og state and navigation ### 2. Utility Functions - permissions.ts: Centralized permission management util...

(QB_NEW_EN_OTHER)


[grammar] ~51-~51: Use correct spacing
Context: ...ts`**: Shared constants and user-facing messages ### 3. Component Refactoring - **Smaller, fo...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~54-~54: There might be a mistake here.
Context: ...mponents**: Each component has a single responsibility - Better separation of concerns: UI logi...

(QB_NEW_EN_OTHER)


[grammar] ~55-~55: Use modal and auxiliary verbs correctly
Context: ...- Better separation of concerns: UI logic separated from business logic - **Impro...

(QB_NEW_EN_OTHER_ERROR_IDS_24)


[grammar] ~55-~55: There might be a mistake here.
Context: ...rns**: UI logic separated from business logic - Improved reusability: Components are m...

(QB_NEW_EN_OTHER)


[grammar] ~56-~56: There might be a mistake here.
Context: ...lity**: Components are more modular and reusable - Enhanced type safety: Better TypeScrip...

(QB_NEW_EN_OTHER)


[grammar] ~57-~57: There might be a mistake here.
Context: ... type safety**: Better TypeScript types throughout ### 4. Code Organization - **Reduced duplica...

(QB_NEW_EN_OTHER)


[grammar] ~62-~62: Use correct spacing
Context: ...ier to understand and modify individual pieces ## Usage Examples ### Creating a Root Key ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~64-~64: Use correct spacing
Context: ... and modify individual pieces ## Usage Examples ### Creating a Root Key ```tsx import { Crea...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~66-~66: Use correct spacing
Context: ... ## Usage Examples ### Creating a Root Key tsx import { CreateRootKeyButton } from "./create-rootkey-button"; <CreateRootKeyButton /> ### Using Permission Management ```tsx impor...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~73-~73: Use correct spacing
Context: ...tKeyButton /> ### Using Permission Managementtsx import { usePermissions } from "./hooks/use-permissions"; const { state, handlePermissionToggle } = usePermissions({ type: "workspace", selected: permissions, onPermissionChange: setPermissions, }); ### Using Constantstsx import { ROOT_KEY...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~84-~84: Use correct spacing
Context: ...nge: setPermissions, }); ### Using Constantstsx import { ROOT_KEY_MESSAGES } from "./constants"; {ROOT_KEY_MESSAGES.UI.CREATE_ROOT_KEY} ``` ## Benefits 1. Maintainability: Easier...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~94-~94: There might be a mistake here.
Context: ...be reused across different parts of the app 3. Testability: Business logic in hooks i...

(QB_NEW_EN_OTHER)


[grammar] ~95-~95: There might be a mistake here.
Context: ...ess logic in hooks is easier to test in isolation 4. Type Safety: Better TypeScript support...

(QB_NEW_EN_OTHER)


[grammar] ~98-~98: Use correct spacing
Context: ...ncy**: Standardized patterns across all components ## Migration Notes The refactoring maintai...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~100-~100: Use correct spacing
Context: ...rns across all components ## Migration Notes The refactoring maintains backward compa...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Build / Build
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (37)
internal/ui/src/index.ts (1)

22-22: LGTM! Export is properly positioned and consistent.

The export for the search-icon component is correctly added after the main llm-search export, maintaining logical grouping and making the SearchIcon component available for use in the root-key UI components.

internal/icons/src/icons/minus.tsx (1)

15-38: LGTM! Well-implemented icon component following established patterns.

The Minus icon component correctly follows the established icon patterns in the codebase:

  • Properly imports and uses the IconProps and sizeMap from props
  • Uses the default "xl-thin" size consistent with other icons
  • Correctly implements the SVG with proper dimensions, stroke properties, and accessibility features
  • Path data "M3.25 9H14.75" creates a proper horizontal line centered in the 18x18 viewBox

This icon will be used for the indeterminate state in checkboxes as part of the root key permission UI enhancements.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/highlighted-text.tsx (1)

15-17: Robust regex implementation with proper escaping.

The implementation correctly escapes special regex characters and creates a case-insensitive global regex with capturing groups. This will safely handle user input containing regex metacharacters.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/README.md (1)

1-102: Comprehensive documentation with clear architecture overview.

The README provides excellent documentation of the refactored root key components, clearly outlining the architectural improvements, directory structure, and usage examples. The separation of concerns into hooks, utilities, constants, and focused components demonstrates good software engineering practices.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (2)

30-36: Verify CheckedState handling logic.

The logic maps indeterminate state to false and toggles boolean states, but this differs from the existing permission toggle implementation in the codebase which uses TRPC mutations and optimistic updates.

This component appears to be a simpler version compared to the existing apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permission_toggle.tsx which includes server-side mutations. Verify that this new component is intended for local state management only and that server-side persistence is handled elsewhere in the permission management flow.


26-50: Clean UI implementation with good accessibility.

The component provides a well-structured UI with:

  • Proper checkbox interaction using Radix CheckedState
  • Clear visual hierarchy with category → label flow
  • InfoTooltip for additional context
  • Hover states and responsive styling

The implementation follows the design system patterns established in the codebase.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (2)

1-6: LGTM! Well-structured constants module.

The constants are properly typed with as const for literal types and provide sensible defaults. The environment variable follows the required naming convention.


8-56: LGTM! Comprehensive message catalog for the Root Key UI.

The message structure is well-organized with clear categorization (SUCCESS, ERROR, WARNING, UI, PLACEHOLDERS, DESCRIPTIONS). The as const assertion ensures type safety for all string literals.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/utils/permissions.ts (2)

1-201: LGTM! Well-structured permission management utilities.

The implementation provides a comprehensive and type-safe permission management system with proper state management through a reducer pattern. The utility functions are well-organized and the use of CheckedState for tri-state checkboxes is appropriate for hierarchical permission structures.


97-98: Optimize permission length comparison.

Consider checking for an exact match first before computing the full comparison, as it's more efficient.

   // Compute root checked state
   let rootChecked: CheckedState = false;
   if (selectedPermissions.length === 0) {
     rootChecked = false;
-  } else if (selectedPermissions.length === allPermissionNames.length) {
+  } else if (selectedPermissions.length === allPermissionNames.length && 
+             allPermissionNames.every(p => selectedPermissions.includes(p))) {
     rootChecked = true;
   } else {
     rootChecked = "indeterminate";
   }

Likely an incorrect or invalid review comment.

internal/icons/src/index.ts (1)

71-71: LGTM! Correctly added Minus icon export.

The new export is properly placed in alphabetical order between "magnifier" and "moon-stars".

apps/dashboard/app/(app)/settings/root-keys/navigation.tsx (1)

7-7: LGTM: switch to dedicated CreateRootKeyButton

The import looks correct and aligns with the modular create flow introduced by the PR.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permissions.ts (1)

26-35: All usePermissions calls with type="api” supply an api prop
Verified that PermissionContentList (in permission-sheet.tsx) always passes api when type="api", and there are no other direct usePermissions usages. The empty‐object fallback only applies if someone misuses the hook in the future. Adding a development‐only guard is optional but not required to protect current callers.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-input.tsx (3)

20-33: LGTM! Well-structured component with clear props and responsibilities

The component has a clear separation of concerns with appropriate prop naming and types. The TypeScript types are properly defined, and the component follows React best practices.


34-41: LGTM! Clear conditional rendering logic

The loading state display logic is appropriately implemented with proper conditional rendering based on searchMode.


43-59: LGTM! Well-implemented input with appropriate accessibility attributes

The input implementation properly uses forwarded refs, includes data-testid for testing, and has appropriate disabled state handling.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (2)

133-159: LGTM! Well-organized permission display structure

The ScrollArea implementation with PermissionBadgeList components provides a clean UI for displaying selected permissions with proper separation between workspace and API permissions.


161-167: RootKeySuccess already guards against undefined props

The RootKeySuccess component immediately returns null when either keyValue or keyId is falsy (see root-key-success.tsx:42–44), so it won’t render if those props are undefined. No further changes required.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (3)

73-82: LGTM! Clean conditional rendering with proper null checks

The component properly calculates workspace vs API display text and safely handles optional api properties.


116-129: LGTM! Well-structured permission rendering with search highlighting

The implementation properly highlights search terms and maps permissions with appropriate descriptions.


42-54: Potential memory leak with effect dependencies

The effect updates state based on searchValue and filteredPermissionList, but the state setters are not memoized. This could cause unnecessary re-renders.

Consider using useMemo for the expanded state logic:

- // State to track expanded categories and root
- const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
- const [isRootExpanded, setIsRootExpanded] = useState(false);
-
- // Auto-expand when search is active
- useEffect(() => {
-   if (searchValue && searchValue.trim() !== "") {
-     setIsRootExpanded(true);
-     setExpandedCategories(new Set(Object.keys(filteredPermissionList)));
-   } else {
-     setIsRootExpanded(false);
-     setExpandedCategories(new Set());
-   }
- }, [searchValue, filteredPermissionList]);

+ const { isRootExpanded, expandedCategories } = useMemo(() => {
+   const hasSearch = searchValue && searchValue.trim() !== "";
+   return {
+     isRootExpanded: hasSearch,
+     expandedCategories: hasSearch ? new Set(Object.keys(filteredPermissionList)) : new Set()
+   };
+ }, [searchValue, filteredPermissionList]);
+
+ const [localExpandedCategories, setLocalExpandedCategories] = useState(expandedCategories);
+ 
+ useEffect(() => {
+   setLocalExpandedCategories(expandedCategories);
+ }, [expandedCategories]);

Likely an incorrect or invalid review comment.

internal/ui/src/components/form/checkbox.tsx (5)

4-4: LGTM! Proper icon imports for tri-state checkbox support

The addition of the Minus icon import alongside Check enables proper visualization of the indeterminate state.


11-11: LGTM! Group class added for state-based icon visibility

The addition of the group class to the root element enables the use of group-based data-state selectors for showing/hiding icons.


18-18: LGTM! Comprehensive indeterminate state styling

All checkbox variants (primary, outline, ghost) and color schemes (danger, warning, success) now properly support the indeterminate state with consistent styling.

Also applies to: 25-25, 32-32, 64-64, 75-75, 86-86, 98-98, 109-109, 120-120, 132-132, 143-143


238-238: LGTM! Type definition properly supports tri-state checkbox

The checked prop type now correctly supports boolean | "indeterminate", enabling tri-state checkbox functionality.


320-321: LGTM! Clean implementation of state-based icon rendering

The icons are properly shown/hidden based on the checkbox state using group-based data-state selectors.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1)

25-172: Well-structured success dialog implementation

The component is well-organized with clear separation of concerns through the useRootKeySuccess hook. The UI provides comprehensive feedback about the created key with appropriate warnings about key visibility.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts (1)

27-28: Remove console.error from production code

Console statements should be removed from production code.

     if (!pendingAction) {
-      console.error(ROOT_KEY_MESSAGES.ERROR.NO_PENDING_ACTION);
+      // Log to error monitoring service instead
       return;
     }
⛔ Skipped due to learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3196
File: apps/dashboard/lib/audit.ts:105-108
Timestamp: 2025-04-25T14:16:27.152Z
Learning: The dashboard app in the Unkey codebase uses direct console.* methods (console.info, console.error, etc.) for logging rather than a structured logger utility.
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1)

30-141: Clean implementation of permission sheet with good separation of concerns

The component effectively manages permissions with search functionality and pagination. The use of the custom hook usePermissionSheet provides good separation of business logic.

apps/dashboard/components/ui/sheet.tsx (2)

30-34: Overlay variant implementation looks good

The introduction of overlay variants provides flexibility for different UI flows while maintaining backward compatibility with the default overlay behavior.


80-86: Good implementation of conditional close button

The implementation properly handles the conditional rendering of the close button with the disableClose prop and allows for custom close icons.

apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (2)

177-177: LGTM: Stable selection checks via selectedRootKeyId

Switching to id-based selection checks is correct and prevents unnecessary re-renders due to object identity changes.

Also applies to: 224-224, 235-235, 261-262


156-167: No changes needed for existingKey mapping

The TRPC schema guarantees that editingKey.permissions is always a defined array (via z.array(PermissionResponse)), and the underlying database enforces unique key-permission pairs, preventing duplicates. Casting each p.name to UnkeyPermission aligns with the expected prop type in RootKeyDialogProps.

• RootKeyResponse defines permissions: z.array(PermissionResponse) → never undefined
• DB constraints on keysPermissions prevent duplicate entries → no dedup needed
• The existing cast to UnkeyPermission correctly matches the dialog’s prop type

Likely an incorrect or invalid review comment.

apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx (1)

12-15: LGTM: Decoupled edit action via onEditKey

Accepting an external onEditKey handler cleanly integrates with the dialog-based edit flow introduced in this PR.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts (3)

96-108: LGTM: hasNoResults is correctly memoized

The search-dependent aggregate check across workspace and API permissions is concise and memoized.


110-118: Add unit tests for permission merging and search callbacks

The merging logic in apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts is non-trivial and easy to regress. A small test suite would lock in expected behavior by covering:

  • handleApiPermissionChange merging logic
  • handleWorkspacePermissionChange merging logic
  • Search/filter behavior (searchValuehasNoResults)

Would you like me to open an issue and follow up with a PR that adds Jest + React Testing Library tests?


3-3: Import path verified; consider centralizing permissions utilities

  • The file apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts exists and matches the import in
    apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts
  • Similar deep-relative imports are used in:
    use-permissions.ts
    permission-badge-list.tsx
    new/client.tsx
  • While these imports are correct today, their brittleness can lead to breakage if the route structure changes. We recommend moving apiPermissions and workspacePermissions into a stable shared module (for example, apps/dashboard/lib/permissions.ts or a centralized barrel) to simplify imports and reduce refactor risk.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/dashboard/lib/trpc/routers/index.ts (1)

154-158: Avoid duplicated surface area: deprecate single-action RBAC endpoints in favor of rootKey.update.permissions

With rootKey.update.permissions in place, rbac.addPermissionToRootKey and rbac.removePermissionFromRootKey overlap and risk fragmentation. Recommend deprecating the single-action endpoints (keep them for backward compatibility) and steering callers to the new canonical route. This was raised previously and still applies.

You can start deprecation by:

  • Adding deprecation notes next to the new canonical export (inline comment above, see prior suggestion).
  • Adding JSDoc deprecation on the RBAC aliases so editors/TS surface warnings.

Outside this hunk, add deprecation JSDoc above the RBAC exports:

/** @deprecated Use rootKey.update.permissions instead (sync authoritative permissions list). */
addPermissionToRootKey: addPermissionToRootKey,

/** @deprecated Use rootKey.update.permissions instead (sync authoritative permissions list). */
removePermissionFromRootKey: removePermissionFromRootKey,

Optionally, plan a follow-up to either:

  • Remove the RBAC add/remove routes after a deprecation window, or
  • Move them under a distinct namespace (e.g., rbac.legacy.*) to make intent explicit.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61bc4eb and f821553.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/dashboard/lib/trpc/routers/index.ts (2 hunks)
  • internal/icons/src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • internal/icons/src/index.ts
  • apps/dashboard/lib/trpc/routers/index.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • internal/icons/src/index.ts
  • apps/dashboard/lib/trpc/routers/index.ts
**/*.{env,js,ts,go}

📄 CodeRabbit Inference Engine (CLAUDE.md)

All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Files:

  • internal/icons/src/index.ts
  • apps/dashboard/lib/trpc/routers/index.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: autofix
🔇 Additional comments (3)
internal/icons/src/index.ts (1)

75-75: No changes needed—barrel export is correct

The minus.tsx file declares a named export (export const Minus…), so export * from "./icons/minus"; correctly re-exports Minus.

apps/dashboard/lib/trpc/routers/index.ts (2)

66-66: LGTM on import; keeps related handlers co-located

Importing updateRootKeyPermissions right next to updateRootKeyName maintains locality and consistency. No issues spotted with naming or path.


154-158: Route addition is consistent with existing update router shape

Adding rootKey.update.permissions mirrors the noun-based keys under the other update routers (e.g., name, metadata). Good alignment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (11)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (2)

38-38: Responsive padding for mobile (pre-existing suggestion)

The fixed px-[120px] overflows on small viewports. Switch to responsive padding.

-          <div className="bg-grayA-2 py-10 flex flex-col items-center justify-center w-full px-[120px]">
+          <div className="bg-grayA-2 py-10 flex flex-col items-center justify-center w-full px-6 sm:px-12 md:px-[120px]">

48-51: Mark decorative icons as aria-hidden and non-focusable

Both icons are decorative. Hide them from a11y tree and prevent focus.

-                  <Key2 size="2xl-thin" aria-hidden="true" />
+                  <Key2 size="2xl-thin" aria-hidden="true" focusable="false" />
                   <div className="flex items-center justify-center border border-grayA-3 rounded-full bg-success-9 text-white size-[22px] absolute right-[-10px] top-[-10px]">
-                    <Check size="sm-bold" />
+                    <Check size="sm-bold" aria-hidden="true" focusable="false" />
                   </div>
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (4)

86-93: Decouple submit loading from background fetching

Use isMutating for the button spinner. Keep background apisLoading out of the primary action’s loading state.

-              disabled={!hasChanges || isMutating}
+              disabled={!hasChanges || isMutating}
               onClick={handleCreateKey}
-              loading={isBusy}
+              loading={isMutating}

146-158: Centralize “Selected from”, “Workspace”, and “from” labels

Replace literals with constants for consistency/i18n.

-              title="Selected from"
-              name="Workspace"
+              title={ROOT_KEY_MESSAGES.UI.PERMISSION_SELECTED_FROM}
+              name={ROOT_KEY_MESSAGES.UI.WORKSPACE}
@@
-                title="from"
+                title={ROOT_KEY_MESSAGES.UI.PERMISSION_FROM}

Add keys in constants.ts (UI section):

  • PERMISSION_SELECTED_FROM: "Selected from"
  • PERMISSION_FROM: "from"
  • WORKSPACE: "Workspace"

98-101: Prefer semantic color tokens to fix light-mode contrast

text-gray-9 may fail contrast in light mode (see PR comment). Use design-system semantic tokens if available.

-            <div className="text-gray-9 text-xs">
+            <div className="text-secondary-foreground text-xs">

Please verify that text-secondary-foreground (or your equivalent) exists in Tailwind config. If not, map one in tailwind.config and apply here.


118-120: Use semantic tokens for labels (light-mode issue)

Similar to above, prefer semantic/muted foreground tokens over raw grays.

-            <Label className="text-[13px] font-regular text-gray-10">
+            <Label className="text-[13px] font-regular text-muted-foreground">

Confirm the token name; adjust to your design system (e.g., text-secondary-foreground or text-muted-foreground).

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (2)

8-15: Rename generic Props to a descriptive type

Improves readability and avoids conflicts.

-type Props = {
+type SearchPermissionsProps = {
   isProcessing: boolean;
   search: string | undefined;
   inputRef: RefObject<HTMLInputElement>;
   onChange: (e: ChangeEvent<HTMLInputElement>) => void;
 };
-const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: Props) => {
+const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: SearchPermissionsProps) => {

30-46: Wire isLoading to actual search state

Currently hardcoded false; reflect real searching when processing a non-empty query.

-  return (
+  const isSearching = isProcessing && (search?.trim().length ?? 0) > 0;
+  return (
@@
-        <SearchInput
+        <SearchInput
           value={search ?? ""}
           placeholder={ROOT_KEY_MESSAGES.UI.SEARCH_PERMISSIONS}
           isProcessing={isProcessing}
-          isLoading={false}
+          isLoading={isSearching}
           loadingText="Searching..."
           clearingText="Clearing..."
           searchMode={SEARCH_MODES.MANUAL}
           onChange={onChange}
           inputRef={inputRef}
         />

Also applies to: 37-41

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (3)

93-94: Prefer human-friendly secondary text

Show the category/description instead of internal ids.

-      renderSecondaryText={(permission) => permission.id}
+      renderSecondaryText={(permission) => permission.description}

98-105: Avoid duplicate className prop in intersection type

className is already in React.ComponentProps.

 type CollapsibleListProps = {
   info: PermissionInfo;
   title: string;
   expandCount: number;
   removePermission: (permission: UnkeyPermission) => void;
   name: string;
-} & React.ComponentProps<typeof CollapsibleTrigger>;
+} & React.ComponentProps<typeof CollapsibleTrigger>;

Remove className from the explicit part if it was declared there earlier (keep relying on ComponentProps).


154-168: Handle wildcard "*" permissions

When "" is selected, the list becomes empty because there’s no exact match. Treat "" as “all permissions”.

 const findPermission = (
   allPermissions: PermissionInfo,
   selectedPermissions: UnkeyPermission[],
 ): PermissionInfo => {
   if (!selectedPermissions || !Array.isArray(selectedPermissions)) {
     return [];
   }
+  // Treat wildcard as "all permissions"
+  if (selectedPermissions.includes("*" as UnkeyPermission)) {
+    return allPermissions;
+  }
   return selectedPermissions
     .map((permission) => {
       return allPermissions.find((p) => p.permission === permission);
     })
     .filter((item): item is { permission: UnkeyPermission; category: string; action: string } =>
       Boolean(item),
     );
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 151a701 and e674664.

📒 Files selected for processing (5)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
**/*.{env,js,ts,go}

📄 CodeRabbit Inference Engine (CLAUDE.md)

All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
📚 Learning: 2024-12-03T14:07:45.173Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2024-10-23T16:19:42.049Z
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the `FilterableCommand` component in `apps/www/components/glossary/search.tsx`, adding error handling and loading states to the results list is not necessary.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
📚 Learning: 2024-10-04T20:44:38.489Z
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Build / Build
  • GitHub Check: Analyze (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1)

38-38: Responsive padding: avoid fixed 120px on small screens

Fixed px-[120px] causes overflow on mobile. Use responsive padding to preserve spacing on larger screens without breaking small viewports.

-          <div className="bg-grayA-2 py-10 flex flex-col items-center justify-center w-full px-[120px]">
+          <div className="bg-grayA-2 py-10 flex flex-col items-center justify-center w-full px-6 sm:px-12 md:px-[120px]">
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)

1-7: Avoid hard-coding API_URL; support environment configuration with UNKEY-prefixed vars

Make API_URL configurable for different environments and follow the repository env var convention. Keep a sensible default as fallback.

 export const ROOT_KEY_CONSTANTS = {
   DEFAULT_LIMIT: 10,
   UNNAMED_KEY: "Unnamed",
-  API_URL: "https://api.unkey.com",
+  API_URL:
+    process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_API_URL ??
+    process.env.NEXT_PUBLIC_UNKEY_API_URL ??
+    "https://api.unkey.com",
   EXPAND_COUNT: 3,
   WORKSPACE: "workspace",
 } as const;
  • Ensure NEXT_PUBLIC_UNKEY_DASHBOARD_API_URL is added to .env.example and deployment configs.
  • Confirm no SSR usage requires a server-only env (if so, mirror with UNKEY_DASHBOARD_API_URL on the server side).
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (2)

92-94: Show friendly secondary text (category/description) instead of repeating the id

Secondary text currently mirrors the id; use the friendlier category we already pass as description.

   <SelectedItemsList
     className="pt-2 overflow-hidden"
     items={info.map((permission) => ({
       id: permission.permission.toString(),
       name: permission.action,
-      description: permission.category,
+      description: permission.category,
     }))}
@@
-      renderSecondaryText={(permission) => permission.id}
+      renderSecondaryText={(permission) => permission.description}

Also applies to: 83-87


154-168: Wildcard permission (“”) not handled — selected “” renders nothing

UnkeyPermission includes "" but findPermission maps only exact matches. When "" is selected, the list becomes empty. Treat "*" as “all permissions” (or render a single “All permissions” badge if that’s the desired UX).

 const findPermission = (
   allPermissions: PermissionInfo,
   selectedPermissions: UnkeyPermission[],
 ): PermissionInfo => {
   if (!selectedPermissions || !Array.isArray(selectedPermissions)) {
     return [];
   }
+  // Treat wildcard as "all permissions"
+  if (selectedPermissions.includes("*" as UnkeyPermission)) {
+    return allPermissions;
+  }
   return selectedPermissions
     .map((permission) => {
       return allPermissions.find((p) => p.permission === permission);
     })
     .filter((item): item is { permission: UnkeyPermission; category: string; action: string } =>
       Boolean(item),
     );
 };

If you prefer rendering a single “All permissions” badge instead of expanding all, I can provide an alternate implementation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e674664 and 3daf846.

📒 Files selected for processing (3)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
**/*.{env,js,ts,go}

📄 CodeRabbit Inference Engine (CLAUDE.md)

All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: For repo unkeyed/unkey and PR review workflows: When imeyer comments "issue" on a thread, automatically create a thorough GitHub issue (sections: Summary, Impact, Where, Repro/Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and the specific comment, and assign the issue to imeyer.
📚 Learning: 2025-08-04T07:44:39.438Z
Learnt from: CR
PR: unkeyed/unkey#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T07:44:39.438Z
Learning: Applies to **/*.{env,js,ts,go} : All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
📚 Learning: 2025-07-21T18:05:58.236Z
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{env,sh,yaml,yml,json,conf,ini} : All environment variables MUST follow the format UNKEY_<SERVICE_NAME>_VARNAME.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
📚 Learning: 2024-12-03T14:07:45.173Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
📚 Learning: 2024-10-04T20:44:38.489Z
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
🧬 Code Graph Analysis (2)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (7)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/permissions.ts (2)
  • workspacePermissions (10-153)
  • apiPermissions (155-206)
apps/dashboard/components/selected-item-list.tsx (1)
  • SelectedItemsList (35-126)
internal/icons/src/icons/key-2.tsx (1)
  • Key2 (15-73)
internal/icons/src/icons/caret-right.tsx (1)
  • CaretRight (15-37)
internal/ui/src/components/badge.tsx (1)
  • Badge (47-47)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts (1)
  • useRootKeySuccess (7-29)
internal/icons/src/icons/key-2.tsx (1)
  • Key2 (15-73)
internal/icons/src/icons/check.tsx (1)
  • Check (15-38)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-59)
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/secret-key.tsx (1)
  • SecretKey (12-54)
internal/icons/src/icons/circle-info.tsx (1)
  • CircleInfo (15-56)
apps/dashboard/components/confirmation-popover.tsx (1)
  • ConfirmPopover (51-116)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Analyze (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (1)
apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (1)

32-41: Add aria-label to fallback actions button for accessibility

The fallback button rendered while actions are loading lacks an accessible name.

Apply this diff:

-    loading: () => (
-      <button
-        type="button"
+    loading: () => (
+      <button
+        type="button"
+        aria-label="Open root key actions"
         className={cn(
           "group-data-[state=open]:bg-gray-6 group-hover:bg-gray-6 group size-5 p-0 rounded m-0 items-center flex justify-center",
           "border border-gray-6 group-hover:border-gray-8 ring-2 ring-transparent focus-visible:ring-gray-7 focus-visible:border-gray-7",
         )}
       >
         <Dots className="group-hover:text-gray-12 text-gray-11" size="sm-regular" />
       </button>
     ),
♻️ Duplicate comments (5)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (2)

7-14: Rename Props to SearchPermissionsProps and export the type

A more descriptive name avoids generic collisions and improves readability. Also consider exporting the type for reuse.

-type Props = {
+export type SearchPermissionsProps = {
   isProcessing: boolean;
   search: string | undefined;
   inputRef: RefObject<HTMLInputElement>;
   onChange: (e: ChangeEvent<HTMLInputElement>) => void;
 };
-const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: Props) => {
+const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: SearchPermissionsProps) => {

13-16: Wire isLoading to actual search state (avoid always showing “Clearing…”)

Currently isLoading={false}, so when isProcessing is true the component always renders “Clearing…”. Tie isLoading to a meaningful “isSearching” condition.

-const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: Props) => {
-  return (
+const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: Props) => {
+  const isSearching = isProcessing && Boolean(search?.trim());
+  return (
@@
-          isProcessing={isProcessing}
-          isLoading={false}
+          isProcessing={isProcessing}
+          isLoading={isSearching}

Also applies to: 21-25

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (2)

76-79: Avoid magic numbers in class calc — extract named class constants (Tailwind-safe)

Extract these calc classes into named constants for clarity and reuse, without introducing dynamic class generation that could confuse Tailwind JIT.

-            <div
-              className={`flex flex-col ${
-                hasNextPage ? "max-h-[calc(100%-80px)]" : "max-h-[calc(100%-40px)]"
-              }`}
-            >
+            <div
+              className={`flex flex-col ${hasNextPage ? MAX_H_WITH_PAGINATION : MAX_H_NO_PAGINATION}`}
+            >

Add near the top of the file (module scope):

// Keep these as literal class strings so Tailwind can statically detect them.
const MAX_H_WITH_PAGINATION = "max-h-[calc(100%-80px)]";
const MAX_H_NO_PAGINATION = "max-h-[calc(100%-40px)]";

119-135: Fix typo in Button classes, add accessible name/type, and guard missing loadMore handler

Typo merges mx-auto and rounded-lg; also show the button only when a handler exists, add aria-label and type="button".

-            <div className="sticky bottom-0 bg-background border-t border-gray-4 mt-auto">
-              {hasNextPage ? (
+            <div className="sticky bottom-0 bg-background border-t border-gray-4 mt-auto">
+              {hasNextPage && loadMore ? (
                 <div className="w-full py-4">
                   <div className="flex flex-row justify-center items-center">
                     <Button
-                      className="mx-autorounded-lg"
+                      type="button"
+                      aria-label={ROOT_KEY_MESSAGES.UI.LOAD_MORE}
+                      className="mx-auto rounded-lg"
                       size="sm"
-                      onClick={loadMore}
-                      disabled={!hasNextPage}
+                      onClick={loadMore}
+                      disabled={!hasNextPage}
                       loading={isFetchingNextPage}
                     >
                       {ROOT_KEY_MESSAGES.UI.LOAD_MORE}
                     </Button>
                   </div>
                 </div>
               ) : undefined}
             </div>
apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (1)

100-101: Empty-state copy fix looks good (role → root key)

The copy was corrected as previously requested. Thanks for addressing it.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3daf846 and 739edb4.

📒 Files selected for processing (3)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
🧠 Learnings (4)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
📚 Learning: 2024-12-03T14:07:45.173Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
📚 Learning: 2024-10-23T16:19:42.049Z
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the `FilterableCommand` component in `apps/www/components/glossary/search.tsx`, adding error handling and loading states to the results list is not necessary.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
📚 Learning: 2024-12-03T14:17:08.016Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/app/(app)/logs/logs-page.tsx:77-83
Timestamp: 2024-12-03T14:17:08.016Z
Learning: The `<LogsTable />` component already implements virtualization to handle large datasets efficiently.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
🧬 Code Graph Analysis (3)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (2)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-input.tsx (2)
  • SearchInput (25-63)
  • SEARCH_MODES (5-9)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-59)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (5)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts (1)
  • usePermissionSheet (14-117)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-59)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (1)
  • PermissionContentList (20-142)
internal/db/src/schema/apis.ts (1)
  • apis (8-27)
apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (8)
apps/dashboard/lib/trpc/routers/settings/root-keys/query.ts (1)
  • RootKey (35-35)
apps/dashboard/app/(app)/settings/root-keys/components/table/utils/get-row-class.ts (1)
  • getRowClassName (26-38)
apps/dashboard/components/virtual-table/types.ts (1)
  • Column (13-24)
apps/dashboard/app/(app)/settings/root-keys/components/table/components/skeletons.tsx (6)
  • RootKeyColumnSkeleton (4-13)
  • KeyColumnSkeleton (20-25)
  • CreatedAtColumnSkeleton (15-19)
  • PermissionsColumnSkeleton (27-34)
  • LastUpdatedColumnSkeleton (36-41)
  • ActionColumnSkeleton (43-53)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx (1)
  • RootKeysTableActions (12-15)
apps/dashboard/components/virtual-table/index.tsx (1)
  • VirtualTable (38-404)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)
  • RootKeyDialog (39-169)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1)

3-3: Good: type-only React imports

Nice use of type-only imports for ChangeEvent and RefObject. This plays well with isolatedModules/tsconfig strictness.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (2)

119-119: Good: switched to a sticky footer for the Load More control

Using a sticky footer avoids content overlap issues that absolute positioning caused.


61-64: Light mode overlay/contrast: please verify

Using overlay="transparent" with bg-gray-1/dark:bg-black can surface underlying content and reduce contrast in light mode (noted in PR comments). Validate in light theme and consider a subtle overlay variant if contrast is poor.

apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (4)

273-274: Ensure action popover clicks don’t trigger row click (stop propagation)

With onRowClick active, clicking the actions trigger can bubble and open the editor unintentionally. Make sure the actions trigger calls e.stopPropagation().

If not already handled in root-keys-table-action.popover.constants.tsx, add:

// Inside the trigger element for the popover
<button
  type="button"
  onClick={(e) => e.stopPropagation()}
  onMouseDown={(e) => e.stopPropagation()}
  aria-label="Open root key actions"
></button>

I can open a quick PR to the actions component if you want.


179-179: Selection state derivation looks consistent and efficient

Switching to selectedRootKeyId for per-cell selection checks is a good micro-optimization that avoids object identity pitfalls.

Also applies to: 226-227, 237-238, 263-264


182-189: Verify light-mode contrast on icon container

Given the PR comment about light mode issues, please double-check contrast for the icon container (bg/border alpha tokens with text-gray-12). The combination of border-grayA-3 bg-grayA-3 and text-gray-12 might be borderline in light mode.

If needed, consider:

  • Using non-alpha tokens for borders in light mode (e.g., border-gray-5, bg-gray-2).
  • Applying theme-aware classes via data-theme or a CSS variable from the design system.

7-13: Imports for permissions typing and dialog composition look correct

Bringing in UnkeyPermission and the RootKeyDialog aligns with the new edit flow and keeps types explicit.

@ogzhanolguncu ogzhanolguncu self-assigned this Aug 18, 2025
@ogzhanolguncu
Copy link
Contributor

ogzhanolguncu commented Aug 19, 2025

image This should `28px` like rest of the app, currently its `32px` Screenshot 2025-08-19 at 16 59 10 image

Hover state doesn't look good it feels like it could use some padding.

Screenshot 2025-08-19 at 17.00.19
These font sizes and weight feels nothing like our design. We might to do some iterations here

image Spacing between number of counts and the Workspace is too much. And workspace and left text feels weird, I think we need to decrease the gap around `Workspace.` Screenshot 2025-08-19 at 17 03 31 I think both text input and `Select permission` should have matching background colors. Reason is they are both inputs and they should blend in. Right now permission selection is way too prominent. image Maybe we can give users a bit more info about what's changed? Like you now have X amount of permission or removed X amount of permission now your total is X. That would be a better UX/UI

Other than that everything works as expected.

@ogzhanolguncu
Copy link
Contributor

And, @perkinsjr or @MichaelUnkey can one of you skim over the coderabbit stuff. It sometimes find critical bugs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
internal/icons/src/icons/check.tsx (1)

20-26: Avoid double-passing focusable; destructure to make intent clear

{...props} already forwards focusable. Passing it again is redundant and risks confusion about which value wins. Destructure and forward explicitly.

Apply this refactor:

-export const Check: React.FC<IconProps> = ({ size = "xl-thin", ...props }) => {
+export const Check: React.FC<IconProps> = ({ size = "xl-thin", focusable, ...rest }) => {
   const { size: pixelSize, strokeWidth } = sizeMap[size];

   return (
     <svg
-      {...props}
+      {...rest}
       height={pixelSize}
       width={pixelSize}
       viewBox="0 0 18 18"
       xmlns="http://www.w3.org/2000/svg"
-      focusable={props.focusable}
+      focusable={focusable}
     >
apps/dashboard/app/(app)/settings/root-keys/components/controls/components/root-keys-filters/index.tsx (3)

78-79: A11y: Update aria-label to reflect Root Keys context (currently says “Filter logs”).

This looks copied from Logs; it can mislead screen readers in Settings > Root Keys.

Apply this diff:

-          aria-label="Filter logs"
+          aria-label="Filter root keys"

83-83: Icon sizing: align with 28px spec noted in design feedback.

Review feedback called out 28px icons elsewhere. Consider bumping the icon size for consistency.

-          <BarsFilter className="text-accent-9 size-4" />
+          <BarsFilter className="text-accent-9 size-7" />

75-77: Minor spacing polish: add a gap between icon/label/badge to improve hover/visual balance.

Helps with the “hover state feels poor / needs padding” feedback without altering layout.

-          className={cn(
-            "group-data-[state=open]:bg-gray-4 px-2 rounded-lg",
-            filters.length > 0 ? "bg-gray-4" : "",
-          )}
+          className={cn(
+            "group-data-[state=open]:bg-gray-4 px-2 rounded-lg gap-2",
+            filters.length > 0 ? "bg-gray-4" : "",
+          )}
♻️ Duplicate comments (21)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts (1)

16-27: Route dialog close attempts through the confirmation flow (expose handleDialogOpenChange).

Avoid binding onOpenChange directly to onClose in consumers. Provide a handler that funnels close attempts through handleCloseAttempt so the confirm UI is honored.

Apply this diff to add a safe onOpenChange handler:

   const handleConfirmClose = () => {
     setIsConfirmOpen(false);
     onClose();
   };
 
+  const handleDialogOpenChange = (open: boolean) => {
+    if (!open) {
+      handleCloseAttempt();
+    }
+  };
+
   return {
     isConfirmOpen,
     setIsConfirmOpen,
     dividerRef,
     handleCloseAttempt,
     handleConfirmClose,
+    handleDialogOpenChange,
   };
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)

4-7: Align API_URL with env var convention and fallbacks.

Follow repo guideline UNKEY_<SERVICE_NAME>_VARNAME and support dashboard-specific/public env. Keep a sane default.

-  API_URL: "https://api.unkey.com",
+  API_URL:
+    process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_API_URL ??
+    process.env.NEXT_PUBLIC_UNKEY_API_URL ??
+    "https://api.unkey.com",

Please also add NEXT_PUBLIC_UNKEY_DASHBOARD_API_URL to .env.example and deployment configs.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (2)

139-169: Add error handling around async mutations; await create for proper failure handling.

Wrap the edit and create flows in try/catch to avoid leaving the UI in an inconsistent state, and await the create mutation so failures are catchable.

-  const handleCreateKey = useCallback(async () => {
+  const handleCreateKey = useCallback(async () => {
     if (editMode && existingKey) {
-      // Update existing key name if changed
-      const nameChanged = name !== existingKey.name && name !== "" && name !== null;
-
-      if (nameChanged) {
-        await updateName.mutateAsync({
-          keyId: existingKey.id,
-          name: name && name.length > 0 ? name : null,
-        });
-      }
-      if (
-        selectedPermissions.length > 0 &&
-        !arePermissionArraysEqual(selectedPermissions, existingKey.permissions)
-      ) {
-        // Update permissions using the new bulk update route
-        await updatePermissions.mutateAsync({
-          keyId: existingKey.id,
-          permissions: selectedPermissions,
-        });
-      }
-
-      onOpenChange(false);
+      try {
+        // name/permissions handled in the other diff; retain those calls here
+        // ...
+        onOpenChange(false);
+      } catch (_err) {
+        // Errors are surfaced via mutation onError toasts; do not close the dialog
+        return;
+      }
     } else {
-      // Create new key
-      key.mutate({
-        name: name && name.length > 0 ? name : undefined,
-        permissions: selectedPermissions,
-      });
+      try {
+        // Create new key
+        await key.mutateAsync({
+          name: name.trim().length > 0 ? name.trim() : undefined,
+          permissions: selectedPermissions,
+        });
+        // Success UI handled by RootKeySuccess/Toast; keep dialog open for success flow
+      } catch (_err) {
+        toast.error(ROOT_KEY_MESSAGES.ERROR.ACTION_FAILED, {
+          description: ROOT_KEY_MESSAGES.ERROR.ACTION_FAILED_DESCRIPTION,
+        });
+      }
     }
   }, [

187-189: Fix misleading comment on effect trigger.

The effect only depends on existingKey.

-  // Reset form when dialog opens/closes or when existingKey changes
+  // Reset form when existingKey changes
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1)

37-41: Centralize subtitle string in ROOT_KEY_MESSAGES.

Move the literal into constants for consistency and future i18n.

-        subTitle="Define a new root key and assign permissions"
+        subTitle={ROOT_KEY_MESSAGES.UI.NEW_ROOT_KEY_SUBTITLE}

Add UI.NEW_ROOT_KEY_SUBTITLE to constants as suggested in the constants.ts comment.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (2)

90-93: Map CheckedState to boolean before invoking root toggle.

Pass the intended state instead of ignoring the argument to avoid tri-state inconsistencies.

-          checked={state.rootChecked}
-          setChecked={handleRootToggle}
+          checked={state.rootChecked}
+          setChecked={(next) => handleRootToggle(next === true)}

Note: This requires updating usePermissions.handleRootToggle to accept a desired boolean (see follow-up below).


108-112: Don’t discard the CheckedState for category toggles; forward intent.

Forward the desired state to the handler so category select-all behaves predictably.

-                      checked={state.categoryChecked[category]}
-                      setChecked={() => handleCategoryToggle(category)}
+                      checked={state.categoryChecked[category]}
+                      setChecked={(next) => handleCategoryToggle(category, next === true)}

Follow-up: Update usePermissions.handleCategoryToggle to accept (category: string, desired: boolean).

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (2)

9-16: Remove unused prop from interface.

The component interface definition is clean and well-structured.


46-47: Mark ChevronRight icon as fully decorative.

The icon has text-grayA-8 but should also include focusable attribute for better accessibility.

-            <ChevronRight size="sm-regular" className="text-grayA-8" />
+            <ChevronRight size="sm-regular" className="text-grayA-8" aria-hidden="true" focusable="false" />
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx (1)

43-46: Render description conditionally to avoid unnecessary DOM elements.

When description is undefined or empty, the paragraph element still takes up space in the DOM.

         <div className="flex flex-col text-left min-w-48 w-full">
           <p className="text-sm w-full">{category}</p>
-          <p className="text-xs text-gray-10 w-full truncate">{description}</p>
+          {description && (
+            <p className="text-xs text-gray-10 w-full truncate">{description}</p>
+          )}
         </div>
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1)

39-40: Consider responsive padding for mobile viewports.

The fixed horizontal padding of 120px may cause layout issues on mobile devices.

apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (2)

69-73: Reconsider single-click editing behavior.

Opening the edit dialog on row click might conflict with row selection UX. Users might expect single-click to select and double-click or action menu to edit.


314-319: Consider clearing both editing states together.

When the dialog closes, you're clearing editingKey but not synchronizing with the editDialogOpen state. While this works, it could be cleaner.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (2)

76-78: Consider extracting magic numbers as constants.

The height calculations use magic numbers that affect layout. Extracting them improves maintainability.


119-135: Fix className typo and improve load more button layout.

There's a typo in the button className where two classes are merged together.

                    <Button
-                     className="mx-autorounded-lg"
+                     className="mx-auto rounded-lg"
                      size="sm"
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (3)

98-101: Use semantic color tokens to fix light-mode contrast

Hardcoded gray class can fail in light mode. Prefer semantic tokens from your design system.

-            <div className="text-gray-9 text-xs">
+            <div className="text-secondary-foreground text-xs">

If text-secondary-foreground isn’t available, use the appropriate semantic muted/secondary token defined in your Tailwind config and update accordingly.


118-120: Use semantic color tokens for label text to align with design and light mode

-            <Label className="text-[13px] font-regular text-gray-10">
+            <Label className="text-[13px] font-regular text-secondary-foreground">

Confirm the correct semantic token (e.g., text-muted-foreground) based on the dashboard’s tokens.


146-151: Centralize “Selected from”, “from”, and “Workspace” strings

These literals bypass your ROOT_KEY_MESSAGES and hinder i18n/consistency.

               <PermissionBadgeList
                 selectedPermissions={selectedPermissions}
                 apiId={ROOT_KEY_CONSTANTS.WORKSPACE}
-                title="Selected from"
-                name="Workspace"
+                title={ROOT_KEY_MESSAGES.UI.PERMISSION_SELECTED_FROM}
+                name={ROOT_KEY_MESSAGES.UI.WORKSPACE}
                 expandCount={ROOT_KEY_CONSTANTS.EXPAND_COUNT}
                 removePermission={removePermission}
               />
@@
               <PermissionBadgeList
                 key={api.id}
                 selectedPermissions={selectedPermissions}
                 apiId={api.id}
-                title="from"
+                title={ROOT_KEY_MESSAGES.UI.PERMISSION_FROM}
                 name={api.name}
                 expandCount={ROOT_KEY_CONSTANTS.EXPAND_COUNT}
                 removePermission={removePermission}
               />

Additionally, add the missing keys to constants:

// apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
export const ROOT_KEY_MESSAGES = {
  // ...
  UI: {
    // ...
    PERMISSION_SELECTED_FROM: "Selected from",
    PERMISSION_FROM: "from",
    WORKSPACE: "Workspace",
  },
  // ...
} as const;

Also applies to: 152-161

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (3)

73-79: Outdated comment: no event is being stopped now

The handler no longer receives an event; update the comment to avoid confusion.

-  // Stop propagation to prevent triggering parent collapsible when removing permissions
+  // Remove permission by id emitted from SelectedItemsList
   const handleRemovePermissionClick = (id: string) => {

90-94: A11y and UX: mark decorative icon hidden and show friendlier secondary text

  • Hide the decorative icon from assistive tech.
  • Show the category/description as secondary text instead of repeating the id.
-      renderIcon={() => <Key2 size="sm-regular" className="text-grayA-11" />}
+      renderIcon={() => (
+        <Key2 size="sm-regular" className="text-grayA-11" aria-hidden="true" focusable="false" />
+      )}
@@
-      renderSecondaryText={(permission) => permission.id}
+      renderSecondaryText={(permission) => permission.description}

140-147: Use semantic tokens for light-mode compatibility

Static grays risk contrast issues. Prefer semantic text and badge colors.

-    <span className="text-sm flex-1 text-grayA-10 text-left flex items-center">
+    <span className="text-sm flex-1 text-secondary-foreground text-left flex items-center">
@@
-      <Badge
+      <Badge
         variant="primary"
         size="sm"
-        className="text-[11px] font-normal text-grayA-11 rounded-full px-2 ml-1 py-1 h-[18px] min-w-[22px] border-[1px] border-grayA-3 "
+        className="text-[11px] font-normal text-muted-foreground rounded-full px-2 ml-1 py-1 h-[18px] min-w-[22px] border-[1px] border-border "
       >

Confirm semantic token names in your Tailwind config; adjust to your design system (e.g., text-muted-foreground, border).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 739edb4 and 6cf085e.

📒 Files selected for processing (15)
  • apps/dashboard/app/(app)/settings/root-keys/components/controls/components/root-keys-filters/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (7 hunks)
  • internal/icons/src/icons/check.tsx (1 hunks)
  • internal/icons/src/props.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • internal/icons/src/props.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/controls/components/root-keys-filters/index.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts
  • internal/icons/src/icons/check.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • internal/icons/src/props.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/controls/components/root-keys-filters/index.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts
  • internal/icons/src/icons/check.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
**/*.{env,js,ts,go}

📄 CodeRabbit Inference Engine (CLAUDE.md)

All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Files:

  • internal/icons/src/props.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: For repo unkeyed/unkey and PR review workflows: When imeyer comments "issue" on a thread, automatically create a thorough GitHub issue (sections: Summary, Impact, Where, Repro/Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and the specific comment, and assign the issue to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/runbook-freshness-check.yaml:157-173
Timestamp: 2025-08-08T14:59:52.283Z
Learning: Repo unkeyed/unkey: When a CI/workflow fix is deferred, imeyer prefers a thorough GitHub issue be opened with sections (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References) and assigned to imeyer, including backlinks to the originating PR and comment.
📚 Learning: 2024-10-04T20:44:38.489Z
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-08-13T19:55:19.828Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#3779
File: apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx:39-41
Timestamp: 2025-08-13T19:55:19.828Z
Learning: min-w-48 is not a default Tailwind CSS utility - it requires custom configuration or should be replaced with min-w-[12rem] using arbitrary value syntax.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
📚 Learning: 2025-08-13T19:55:19.828Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#3779
File: apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx:39-41
Timestamp: 2025-08-13T19:55:19.828Z
Learning: The Tailwind CSS truncate utility includes white-space: nowrap behavior along with overflow: hidden and text-overflow: ellipsis, making additional nowrap classes redundant.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
📚 Learning: 2025-08-13T19:55:19.828Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#3779
File: apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx:39-41
Timestamp: 2025-08-13T19:55:19.828Z
Learning: The Tailwind CSS truncate utility already includes white-space: nowrap behavior along with overflow: hidden and text-overflow: ellipsis, making additional nowrap classes redundant.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
📚 Learning: 2025-08-13T19:55:19.828Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#3779
File: apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx:39-41
Timestamp: 2025-08-13T19:55:19.828Z
Learning: In Tailwind CSS v3.4+, min-w-48 is a valid utility class equivalent to min-width: 12rem.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
📚 Learning: 2025-07-11T12:57:39.875Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3528
File: apps/dashboard/app/(app)/settings/team/role-switcher.tsx:60-76
Timestamp: 2025-07-11T12:57:39.875Z
Learning: In the Select component from unkey/ui, when fixing chevron overflow/positioning issues, applying `w-fit` directly to the `SelectTrigger` doesn't resolve the problem. A wrapper div with `w-fit` class around the entire Select component is required to properly contain the chevron within the trigger area.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
📚 Learning: 2024-12-03T14:07:45.173Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
📚 Learning: 2025-08-04T07:44:39.438Z
Learnt from: CR
PR: unkeyed/unkey#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T07:44:39.438Z
Learning: Applies to **/*.{env,js,ts,go} : All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
📚 Learning: 2025-07-21T18:05:58.236Z
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{env,sh,yaml,yml,json,conf,ini} : All environment variables MUST follow the format UNKEY_<SERVICE_NAME>_VARNAME.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
📚 Learning: 2024-12-03T14:17:08.016Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/app/(app)/logs/logs-page.tsx:77-83
Timestamp: 2024-12-03T14:17:08.016Z
Learning: The `<LogsTable />` component already implements virtualization to handle large datasets efficiently.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx
🧬 Code Graph Analysis (10)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (5)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1)
  • useRootKeyDialog (53-232)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (2)
  • ROOT_KEY_MESSAGES (9-61)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1)
  • PermissionSheet (30-141)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1)
  • RootKeySuccess (14-127)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (3)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (2)
  • ROOT_KEY_CONSTANTS (1-7)
  • ROOT_KEY_MESSAGES (9-61)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx (1)
internal/icons/src/icons/caret-right.tsx (1)
  • CaretRight (15-37)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (4)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/permissions.ts (2)
  • workspacePermissions (10-153)
  • apiPermissions (155-206)
apps/dashboard/components/selected-item-list.tsx (1)
  • SelectedItemsList (35-126)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (1)
internal/icons/src/icons/chevron-right.tsx (1)
  • ChevronRight (17-40)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (3)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permission-sheet.ts (1)
  • usePermissionSheet (14-117)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-61)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (1)
  • PermissionContentList (20-144)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (4)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-success.ts (1)
  • useRootKeySuccess (7-28)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-61)
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/secret-key.tsx (1)
  • SecretKey (12-54)
apps/dashboard/components/confirmation-popover.tsx (1)
  • ConfirmPopover (51-116)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (4)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-permissions.ts (1)
  • usePermissions (18-135)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-61)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/highlighted-text.tsx (1)
  • HighlightedText (11-34)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (3)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-61)
internal/icons/src/icons/plus.tsx (1)
  • Plus (16-52)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)
  • RootKeyDialog (39-169)
apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (8)
packages/rbac/src/permissions.ts (2)
  • UnkeyPermission (83-83)
  • unkeyPermissionValidation (87-119)
apps/dashboard/lib/trpc/routers/settings/root-keys/query.ts (1)
  • RootKey (35-35)
apps/dashboard/app/(app)/settings/root-keys/components/table/utils/get-row-class.ts (1)
  • getRowClassName (26-38)
internal/ui/src/components/buttons/button.tsx (1)
  • buttonVariants (439-439)
apps/dashboard/components/virtual-table/types.ts (1)
  • Column (13-24)
apps/dashboard/app/(app)/settings/root-keys/components/table/components/actions/root-keys-table-action.popover.constants.tsx (1)
  • RootKeysTableActions (12-15)
apps/dashboard/components/virtual-table/index.tsx (1)
  • VirtualTable (38-404)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)
  • RootKeyDialog (39-169)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Packages / Test
  • GitHub Check: Build / Build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (17)
internal/icons/src/props.ts (1)

35-36: Adding focusable to IconProps: LGTM

Exposes focus control for SVGs where needed. No breaking changes.

apps/dashboard/app/(app)/settings/root-keys/components/controls/components/root-keys-filters/index.tsx (1)

3-3: Fix residual cn imports and ensure a single source of truth

We’ve confirmed that:

  • cn is correctly exported in apps/dashboard/lib/utils.ts (uses clsx + twMerge).
  • The @/lib/utils alias resolves via apps/dashboard/tsconfig.json ("@/*": ["./*"]).

However, there are still many import sites across apps/dashboard (and even in apps/engineering) pulling cn from @unkey/ui/src/lib/utils. To fully migrate:

• Replace all
import { cn } from "@unkey/ui/src/lib/utils"
with
import { cn } from "@/lib/utils"
(search for "@unkey/ui/src/lib/utils").

• Verify no other utilities are imported from the old path.

• Confirm your root-level TS config (or workspace config) also defines "@/*" if you have consumers outside apps/dashboard.

• Once complete, remove any lingering references to @unkey/ui/src/lib/utils.

This will centralize your cn implementation and avoid class‐merging inconsistencies.

⛔ Skipped due to learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the `api` directory and should be kept consistent with that original implementation.
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-list.tsx (1)

76-83: Good structure: collapsible root with clear header and count.

The auto-expand on search and separation of concerns via usePermissions reads well. Nice.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (2)

18-21: Component implementation looks good.

The forwardRef implementation and use of useId for accessibility is appropriate.


38-39: Good accessibility practices with proper event handling.

The stopPropagation on the checkbox click and the tri-state to boolean conversion in onCheckedChange are correctly implemented.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/expandable-category.tsx (1)

39-40: Good implementation of the collapsible trigger.

The className merging with cn utility and proper forwarding of ref and props is well implemented. The rotation animation for the caret icon is clean.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (3)

25-31: LGTM! Dialog close handling properly routes through confirmation.

The dialog's onOpenChange correctly calls handleCloseAttempt when closing, ensuring the confirmation popover is shown before actual closure.


49-51: Good accessibility implementation for decorative icons.

The decorative icons properly use aria-hidden="true" and focusable={false} attributes.


34-34: Consider responsive design for the dialog width.

The max-w-[760px] works well for desktop but might need adjustment for smaller screens.

Consider adding responsive breakpoints:

-        className="drop-shadow-2xl border-grayA-4 overflow-hidden !rounded-2xl p-0 gap-0 w-full max-w-[760px] max-h-[90vh] overflow-y-auto"
+        className="drop-shadow-2xl border-grayA-4 overflow-hidden !rounded-2xl p-0 gap-0 w-full max-w-full sm:max-w-[760px] max-h-[90vh] overflow-y-auto"

Likely an incorrect or invalid review comment.

apps/dashboard/app/(app)/settings/root-keys/components/table/root-keys-list.tsx (4)

15-19: Good addition of runtime type guard for permissions.

The isUnkeyPermission type guard using Zod validation is a robust approach to ensure type safety at runtime. This prevents potential runtime errors from invalid permission names.


60-63: Clean implementation of edit key handler.

The handleEditKey callback is well-structured and properly memoized.


167-181: Excellent implementation of safe permission validation.

The existingKey memo properly validates permissions using the type guard function, filtering out any invalid permission names. This is a robust defensive programming practice.


111-121: Fix nested interactive elements accessibility issue.

The anchor wrapping a button creates invalid HTML and accessibility problems. This violates WCAG guidelines for interactive elements.

Use the Button's asChild pattern if supported:

           <Empty.Actions className="mt-4 justify-start">
-            <a
-              href="https://www.unkey.com/docs/security/root-keys"
-              target="_blank"
-              rel="noopener noreferrer"
-              className={buttonVariants({ variant: "outline" })}
-            >
-              <span className="flex items-center gap-2">
-                <BookBookmark />
-                Learn about Root Keys
-              </span>
-            </a>
+            <Button asChild variant="outline">
+              <a
+                href="https://www.unkey.com/docs/security/root-keys"
+                target="_blank"
+                rel="noopener noreferrer"
+              >
+                <BookBookmark />
+                Learn about Root Keys
+              </a>
+            </Button>
           </Empty.Actions>

Or use just the anchor with button styling:

-            <a
-              href="https://www.unkey.com/docs/security/root-keys"
-              target="_blank"
-              rel="noopener noreferrer"
-              className={buttonVariants({ variant: "outline" })}
-            >
+            <a
+              href="https://www.unkey.com/docs/security/root-keys"
+              target="_blank"
+              rel="noopener noreferrer"
+              className={buttonVariants({ variant: "outline" })}
+            >

(This option is already correct in your code - the buttonVariants approach doesn't create nested interactive elements)

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (2)

49-53: Hook integration looks correct.

The usePermissionSheet hook is properly initialized with all required props including the onChange handler.


82-85: Good UX for empty search results.

The "No results found" message provides clear feedback when search yields no matches.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)

14-24: Dynamic import fallback is solid

Good use of a role="alert" fallback and SSR disabled for the DialogContainer load.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1)

31-41: LGTM: efficient flattening with memoization

Nice use of useMemo to flatten permission catalogs once per apiId; avoids recompute on unrelated renders.

Copy link
Collaborator

➕ Pushed commit: pr suggested changes

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (13)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1)

33-34: Verify Plus icon renders at 28px as per design

Design feedback calls for a 28px icon. Ensure the token you pass yields 28px (e.g., size="lg-regular" or "xl-regular" depending on your icon sizeMap).

-        <Plus />
+        <Plus size="lg-regular" />

If “lg-regular” isn’t 28px in your icon system, switch to the correct token that maps to 28px.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1)

7-14: Rename Props to a descriptive type to avoid collisions

Use a specific type name for clarity and consistency.

-type Props = {
+type SearchPermissionsProps = {
   isProcessing: boolean;
   search: string | undefined;
   inputRef: RefObject<HTMLInputElement>;
   onChange: (e: ChangeEvent<HTMLInputElement>) => void;
 };
-const SearchPermissions = ({ isProcessing, search, inputRef, onChange }: Props) => {
+const SearchPermissions = ({
+  isProcessing,
+  search,
+  inputRef,
+  onChange,
+}: SearchPermissionsProps) => {
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (3)

26-26: Limit transitions to colors (avoid transition-all)

Only colors change here. Narrow the transition scope to avoid unnecessary reflow.

-          "hover:cursor-pointer flex flex-row items-center justify-start gap-4 transition-all pl-3 h-full mb-1 ml-2 w-full hover:bg-grayA-3 rounded-lg",
+          "hover:cursor-pointer flex flex-row items-center justify-start gap-4 transition-colors pl-3 h-full mb-1 ml-2 w-full hover:bg-grayA-3 rounded-lg",

45-46: Mark ChevronRight icon as decorative

Prevent non-informative icons from being read by screen readers.

-            <ChevronRight size="sm-regular" className="text-grayA-8" />
+            <ChevronRight size="sm-regular" className="text-grayA-8" aria-hidden="true" focusable="false" />

46-47: Remove redundant JSX braces

Minor cleanup.

-            {<span className="text-sm w-full">{label}</span>}
+            <span className="text-sm w-full">{label}</span>
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)

4-4: Use env vars for API_URL in root-key constants

Replace the hard-coded URL with the repo-standard env var lookup and a safe fallback:

--- a/apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
+++ b/apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
@@ lines 1–10
   DEFAULT_LIMIT: 10,
   UNNAMED_KEY: "Unnamed",
-  API_URL: "https://api.unkey.com",
+  API_URL:
+    process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_API_URL ??
+    process.env.NEXT_PUBLIC_UNKEY_API_URL ??
+    "https://api.unkey.dev",
   EXPAND_COUNT: 3,
   WORKSPACE: "workspace",

• Add NEXT_PUBLIC_UNKEY_DASHBOARD_API_URL to .env.example and your deployment config.
• Run rg -n '\bAPI_URL\b' to confirm no other code still relies on the old hard-coded value.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1)

180-181: Fix misleading comment about effect dependencies

The effect only depends on existingKey.

-  // Reset form when dialog opens/closes or when existingKey changes
+  // Reset form when existingKey changes
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (3)

146-149: Centralize “Selected from”, “Workspace”, and “from” strings to constants

Use message constants for consistency/i18n. Add keys and replace literals.

               <PermissionBadgeList
                 selectedPermissions={selectedPermissions}
                 apiId={ROOT_KEY_CONSTANTS.WORKSPACE}
-                title="Selected from"
-                name="Workspace"
+                title={ROOT_KEY_MESSAGES.UI.PERMISSION_SELECTED_FROM}
+                name={ROOT_KEY_MESSAGES.UI.WORKSPACE}
                 expandCount={ROOT_KEY_CONSTANTS.EXPAND_COUNT}
                 removePermission={removePermission}
               />
@@
               <PermissionBadgeList
                 key={api.id}
                 selectedPermissions={selectedPermissions}
                 apiId={api.id}
-                title="from"
+                title={ROOT_KEY_MESSAGES.UI.PERMISSION_FROM}
                 name={api.name}

Add to constants (outside this diff):

// constants.ts
export const ROOT_KEY_MESSAGES = {
  // ...
  UI: {
    // ...
    PERMISSION_SELECTED_FROM: "Selected from",
    PERMISSION_FROM: "from",
    WORKSPACE: "Workspace",
  },
};

Also applies to: 157-158


98-101: Prefer semantic color tokens over raw grays for light-mode contrast

Replace text-gray-9 with a semantic token (e.g., text-secondary-foreground or your system’s muted foreground) to avoid light-mode contrast issues.

-            <div className="text-gray-9 text-xs">
+            <div className="text-secondary-foreground text-xs">

118-120: Use semantic colors for label text

Align with design tokens for consistent theming.

-            <Label className="text-[13px] font-regular text-gray-10">
+            <Label className="text-[13px] font-regular text-muted-foreground">
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (3)

73-79: Outdated comment and narrow the id typing

The handler no longer receives an event; update the comment. Also, type the id as UnkeyPermission to preserve intent.

-  // Stop propagation to prevent triggering parent collapsible when removing permissions
-  const handleRemovePermissionClick = (id: string) => {
+  // Remove permission by id emitted from SelectedItemsList
+  const handleRemovePermissionClick = (id: UnkeyPermission) => {
     const permission = info.find((p) => p.permission === id);
     if (permission) {
       removePermission(permission.permission);
     }
   };

154-168: Handle wildcard “*” and dedupe selections in findPermission

UnkeyPermission includes "*" which should represent all permissions. Also dedupe to prevent duplicate badges.

 const findPermission = (
   allPermissions: PermissionInfo,
   selectedPermissions: UnkeyPermission[],
 ): PermissionInfo => {
   if (!selectedPermissions || !Array.isArray(selectedPermissions)) {
     return [];
   }
-  return selectedPermissions
+  // Treat "*" as all permissions
+  if (selectedPermissions.includes("*")) {
+    return allPermissions;
+  }
+  // Deduplicate selected permissions
+  const seen = new Set<UnkeyPermission>();
+  return selectedPermissions
     .map((permission) => {
-      return allPermissions.find((p) => p.permission === permission);
+      if (seen.has(permission)) return undefined;
+      seen.add(permission);
+      return allPermissions.find((p) => p.permission === permission);
     })
     .filter((item): item is { permission: UnkeyPermission; category: string; action: string } =>
       Boolean(item),
     );
 };

90-94: A11y and UX: hide decorative icon; show friendly secondary text

  • Mark the icon decorative for assistive tech.
  • Show category/description as secondary text instead of the id.
-      renderIcon={() => <Key2 size="sm-regular" className="text-grayA-11" />}
+      renderIcon={() => (
+        <Key2 size="sm-regular" className="text-grayA-11" aria-hidden="true" focusable="false" />
+      )}
@@
-      renderSecondaryText={(permission) => permission.id}
+      renderSecondaryText={(permission) => permission.description}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6cf085e and 5c78b17.

📒 Files selected for processing (7)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
**/*.{env,js,ts,go}

📄 CodeRabbit Inference Engine (CLAUDE.md)

All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
🧠 Learnings (10)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: For repo unkeyed/unkey and PR review workflows: When imeyer comments "issue" on a thread, automatically create a thorough GitHub issue (sections: Summary, Impact, Where, Repro/Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and the specific comment, and assign the issue to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/runbook-freshness-check.yaml:157-173
Timestamp: 2025-08-08T14:59:52.283Z
Learning: Repo unkeyed/unkey: When a CI/workflow fix is deferred, imeyer prefers a thorough GitHub issue be opened with sections (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References) and assigned to imeyer, including backlinks to the originating PR and comment.
📚 Learning: 2024-12-03T14:07:45.173Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx
📚 Learning: 2024-10-04T20:44:38.489Z
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-04-22T17:33:28.162Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#3173
File: apps/docs/security/delete-protection.mdx:32-36
Timestamp: 2025-04-22T17:33:28.162Z
Learning: In the Unkey dashboard UI for delete protection, the button/link to initiate the process is labeled "Disable Delete Protection" while the confirmation button is labeled "Disable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
📚 Learning: 2025-04-22T17:34:04.438Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#3173
File: apps/docs/security/delete-protection.mdx:21-24
Timestamp: 2025-04-22T17:34:04.438Z
Learning: In the Unkey dashboard UI for enabling delete protection, the button/link to initiate the process is labeled "Enable Delete Protection" while the confirmation button is labeled "Enable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
📚 Learning: 2024-10-23T16:19:42.049Z
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the `FilterableCommand` component in `apps/www/components/glossary/search.tsx`, adding error handling and loading states to the results list is not necessary.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx
📚 Learning: 2025-02-06T17:41:47.228Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2876
File: apps/dashboard/components/logs/datetime/constants.ts:96-96
Timestamp: 2025-02-06T17:41:47.228Z
Learning: In the Unkey codebase, avoid hardcoding IDs or indices that depend on array positions, as the arrays may be modified in the future. Instead, use methods like `find` with unique identifiers or properties to locate specific items.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-08-04T07:44:39.438Z
Learnt from: CR
PR: unkeyed/unkey#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T07:44:39.438Z
Learning: Applies to **/*.{env,js,ts,go} : All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
📚 Learning: 2025-07-21T18:05:58.236Z
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{env,sh,yaml,yml,json,conf,ini} : All environment variables MUST follow the format UNKEY_<SERVICE_NAME>_VARNAME.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts
🧬 Code Graph Analysis (6)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (3)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (2)
  • ROOT_KEY_CONSTANTS (1-7)
  • ROOT_KEY_MESSAGES (9-62)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (3)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-62)
internal/icons/src/icons/plus.tsx (1)
  • Plus (16-52)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)
  • RootKeyDialog (39-169)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (5)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1)
  • useRootKeyDialog (46-225)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (2)
  • ROOT_KEY_MESSAGES (9-62)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1)
  • PermissionSheet (30-141)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1)
  • RootKeySuccess (14-127)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-input.tsx (2)
  • SearchInput (25-63)
  • SEARCH_MODES (5-9)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (6)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/permissions.ts (2)
  • workspacePermissions (10-153)
  • apiPermissions (155-206)
apps/dashboard/components/selected-item-list.tsx (1)
  • SelectedItemsList (35-126)
internal/icons/src/icons/key-2.tsx (1)
  • Key2 (15-73)
internal/icons/src/icons/caret-right.tsx (1)
  • CaretRight (15-37)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-toggle.tsx (3)
internal/ui/src/components/form/checkbox.tsx (1)
  • Checkbox (330-330)
internal/icons/src/icons/chevron-right.tsx (1)
  • ChevronRight (17-40)
internal/ui/src/components/info-tooltip.tsx (1)
  • InfoTooltip (63-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages / Test
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1)

15-35: Event composition, ref wiring, and button type are correctly implemented

You’ve addressed the prior concerns: composed onClick, wired triggerRef, and set type="button". Looks good.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/search-permissions.tsx (1)

22-32: Good loading-state wiring for SearchInput

Passing isLoading based on trimmed, non-empty search while processing is correct and prevents “Clearing…” from showing during searches. Nice.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)

44-46: Centralized subtitle looks good and is in use

Good job moving “Define a new root key and assign permissions” into messages and consuming it in the dialog.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (2)

7-34: Set-based permission equality is correct and efficient

O(n) membership check via Set avoids sorting allocations and is suitable for this use-case.


121-129: Permission change gating logic is clearer now

The simplified “canUpdate” condition improves readability and matches intent.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)

14-24: Dynamic import fallback is solid

Good use of dynamic import with a graceful, accessible fallback and SSR disabled.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1)

30-41: Flattening permissions via useMemo is correct and efficient

Deriving a stable flattened list from apiId keeps memoization effective. Good use of workspace vs per-API catalogs.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (8)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1)

20-35: Add dialog a11y attributes to the trigger

Expose dialog state via ARIA so assistive tech understands the control opens a dialog.

       <Button
         {...props}
         title={ROOT_KEY_MESSAGES.UI.NEW_ROOT_KEY}
         onClick={(e) => {
           onClick?.(e);
           setIsOpen(true);
         }}
         ref={triggerRef}
         variant="primary"
         type="button"
         size="sm"
+        aria-haspopup="dialog"
+        aria-expanded={isOpen}
         className={cn("px-3 rounded-md", className)}
       >
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (2)

86-93: Decouple submit state from background fetch; remove redundant disable condition

Primary action shouldn’t spin/disable due to background API fetching. Also, !selectedPermissions.length duplicates the create-mode logic already covered by hasChanges.

             <Button
               variant="primary"
               size="xlg"
               className="w-full rounded-lg"
-              disabled={!hasChanges || isMutating || !selectedPermissions.length}
+              disabled={!hasChanges || isMutating}
               onClick={handleCreateKey}
-              loading={isBusy}
+              loading={isMutating}
+              aria-busy={isMutating}
             >

131-137: Permission sheet trigger: align colors with semantic tokens

Avoid hard-coded grays. Use design-system tokens for text, borders, bg, and focus rings.

               <Button
                 type="button"
                 variant="outline"
                 size="lg"
-                className="rounded-lg font-light text-grayA-8 text-[13px] border border-gray-5 hover:border-gray-8 bg-gray-2 dark:bg-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-5 focus-visible:ring-offset-0"
+                className="rounded-lg font-light text-[13px] text-muted-foreground border border-border hover:border-foreground/40 bg-muted dark:bg-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0"
                 disabled={isBusy}
               >

Run the Tailwind token check (see earlier script) and adjust tokens to match your config if names differ.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (5)

90-94: Prefer description for secondary text (id is already present internally)

Show the human-friendly category/description instead of the internal id.

-      renderSecondaryText={(permission) => permission.id}
+      renderSecondaryText={(permission) => permission.description}

90-91: A11y: mark icon as non-focusable

The icon is decorative; add focusable={false} so it can’t receive focus.

-      renderIcon={() => <Key2 size="sm-regular" className="text-grayA-11" aria-hidden="true" />}
+      renderIcon={() => (
+        <Key2 size="sm-regular" className="text-grayA-11" aria-hidden="true" focusable={false} />
+      )}

73-79: Update outdated comment (no event to stop propagation anymore)

The handler no longer receives an event; fix the comment to reflect actual behavior.

-  // Stop propagation to prevent triggering parent collapsible when removing permissions
+  // Remove permission by id emitted from SelectedItemsList
   const handleRemovePermissionClick = (id: string) => {

154-168: Handle wildcard "*" and dedupe selections to avoid empty/duplicate badges

UnkeyPermission includes "*". Treat it as “all permissions” and dedupe to avoid duplicate items.

 const findPermission = (
   allPermissions: PermissionInfo,
   selectedPermissions: UnkeyPermission[],
 ): PermissionInfo => {
   if (!selectedPermissions || !Array.isArray(selectedPermissions)) {
     return [];
   }
-  return selectedPermissions
+  // Treat "*" as all permissions
+  if (selectedPermissions.includes("*")) {
+    return allPermissions;
+  }
+  // Deduplicate to avoid duplicate badges
+  const seen = new Set<UnkeyPermission>();
+  return selectedPermissions
     .map((permission) => {
-      return allPermissions.find((p) => p.permission === permission);
+      if (seen.has(permission)) return undefined;
+      seen.add(permission);
+      return allPermissions.find((p) => p.permission === permission);
     })
     .filter((item): item is { permission: UnkeyPermission; category: string; action: string } =>
       Boolean(item),
     );
 };

140-147: Use semantic tokens for text/border colors for theme consistency

Replace hard-coded grays with semantic tokens.

-    <span className="text-[13px] flex-1 text-grayA-10 text-left flex items-center">
+    <span className="text-[13px] flex-1 text-muted-foreground text-left flex items-center">
       {title}
-      <span className="font-normal text-grayA-12 ml-1 font-mono">{category}</span>
+      <span className="font-normal text-foreground ml-1 font-mono">{category}</span>
       <Badge
         variant="primary"
         size="sm"
-        className="text-[11px] font-normal text-gray-11 rounded-full px-2 ml-1 py-1 h-[18px] min-w-[22px] border-[1px] border-grayA-3 "
+        className="text-[11px] font-normal text-foreground rounded-full px-2 ml-1 py-1 h-[18px] min-w-[22px] border-[1px] border-border "
       >
         {count}
       </Badge>

Verify token availability in your Tailwind config if needed (see earlier script).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c78b17 and fcff7bd.

📒 Files selected for processing (3)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1 hunks)
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects

Files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
🧠 Learnings (4)
📓 Common learnings
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/actions/setup-node/action.yaml:0-0
Timestamp: 2025-08-08T15:10:46.436Z
Learning: Repo: unkeyed/unkey — Preference: If imeyer comments “issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: imeyer
PR: unkeyed/unkey#3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
📚 Learning: 2024-10-04T20:44:38.489Z
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
📚 Learning: 2024-12-03T14:07:45.173Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

Applied to files:

  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx
  • apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx
🧬 Code Graph Analysis (3)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (5)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/hooks/use-root-key-dialog.ts (1)
  • useRootKeyDialog (46-225)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (2)
  • ROOT_KEY_MESSAGES (9-62)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-sheet.tsx (1)
  • PermissionSheet (30-141)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-success.tsx (1)
  • RootKeySuccess (14-127)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx (6)
packages/rbac/src/permissions.ts (1)
  • UnkeyPermission (83-83)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_CONSTANTS (1-7)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/permissions.ts (2)
  • workspacePermissions (10-153)
  • apiPermissions (155-206)
apps/dashboard/components/selected-item-list.tsx (1)
  • SelectedItemsList (35-126)
internal/icons/src/icons/key-2.tsx (1)
  • Key2 (15-73)
internal/icons/src/icons/caret-right.tsx (1)
  • CaretRight (15-37)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (3)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/constants.ts (1)
  • ROOT_KEY_MESSAGES (9-62)
internal/icons/src/icons/plus.tsx (1)
  • Plus (16-52)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)
  • RootKeyDialog (39-169)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/dashboard/app/(app)/settings/root-keys/components/root-key/create-rootkey-button.tsx (1)

23-31: Good fix: composed onClick, forwarded ref, and explicit type="button"

You avoided clobbering consumer onClick, wired triggerRef, and set type="button". This addresses the earlier concerns cleanly.

apps/dashboard/app/(app)/settings/root-keys/components/root-key/root-key-dialog.tsx (1)

98-102: Use a semantic text color token for better theming
Replace the raw gray utility with a semantic token to guarantee proper light-/dark-mode contrast.

-            <div className="text-gray-9 text-xs">
+            <div className="text-muted-foreground text-xs">

If text-muted-foreground isn’t defined in your Tailwind config, pick the closest semantic alternative (for example, text-secondary-foreground). You can search your configs for available tokens with:

#!/bin/bash
fd -HI 'tailwind.config.*' -x sh -c '
  echo "== {} ==";
  rg -n "muted-foreground\\|secondary-foreground" -C3 {}
'

Copy link
Contributor

@ogzhanolguncu ogzhanolguncu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@graphite-app
Copy link

graphite-app bot commented Aug 20, 2025

Illustrated gif. A hand appears and holds up an oversized thumb, giving us a thumbs up. (Added via Giphy)

@perkinsjr perkinsjr enabled auto-merge August 20, 2025 15:15
@graphite-app
Copy link

graphite-app bot commented Aug 20, 2025

Graphite Automations

"Post a GIF when PR approved" took an action on this PR • (08/20/25)

1 gif was posted to this PR based on Andreas Thomas's automation.

@perkinsjr perkinsjr disabled auto-merge August 20, 2025 15:31
@perkinsjr perkinsjr merged commit 672c2e4 into main Aug 20, 2025
16 checks passed
@perkinsjr perkinsjr deleted the root-key-redisign branch August 20, 2025 15:31
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.

3 participants