Drawer sharing UI#587
Open
jxjj wants to merge 29 commits into
Open
Conversation
Wires the Rules tab up to the grant endpoints the API already exposes. - `ownerName` on the grant's group, so a grant for someone else's group can name its owner - create takes drawer + group + level, update takes only the level: a grant's drawer and group are fixed once created - bodies are form-urlencoded, matching the drawer group fetchers
The drawer Rules tab needs the same permission picker, chip dots, and level options. Both pages now import them from `@/components`, rather than the drawer page reaching into a sibling page for them. Pure move: no behavior change.
Brings the Rules tab up to what the admin Rules tab already does. - sortable columns, defaulting to drawer ascending - one search box filtering every text column - the permission renders as a chip with its level's dot color - the drawer title links to its contents, and a group of the caller's own links to its row on the Groups tab - a group owned by someone else shows a lock and the owner's name: the Groups tab lists only the caller's own groups, so it has no row to link to, though the grant stays editable - Edit swaps the permission cell for a select, holding the submitted level with a spinner until the refetch lands Rows flatten the join so sorting and filtering read plain accessor values.
The dialog names the group, permission, and drawer being revoked, and the row grays out until the refetch drops it. Revoking a grant for a group the caller does not own is called out hard: managing a drawer carries the right to revoke such a grant but not to recreate one, since a new rule can only name a group the caller owns. That delete is one-way, so the dialog says so instead of the usual "cannot be undone".
Create Rule picks a drawer, a group, and a permission. New Group and New Drawer open a second modal over the first and select what they create, so a rule can be built without leaving the form. - the group picker offers only the caller's own groups, which is all a new rule can name - picking a drawer and group that already have a rule warns and saves as a replacement, rather than tripping the API's duplicate check - a group created mid-rule has nobody in it yet, so closing offers a jump to its members or entries The Groups tab now answers the ?group deep link the Rules tab links to, matching the admin Groups tab.
Deleting a rule for a group the caller does not own is one-way: a new rule can only name a group they own, so they could not put it back. - the Delete action is gone for those rules - the permission editor offers No Permissions, which revokes the access and leaves the rule for its owner to see No Permissions stays out of the create form, where a level-0 rule would grant nothing and mean nothing.
House style calls out merged create/update helpers, and the `kind` union was pushing narrowing into call sites that only ever take one branch. `useSaveDrawerGrantMutation` becomes create and update hooks. - the two in-flight save refs collapse into one `savingRule` - `expandGroupRow` names the ExpandedState sentinel dance the deep link and the create path both do - comment fixes: drop restatements, name subjects instead of pronouns, keep each fact where it is produced Adds component tests pinning the ?group deep link: the row is focused and the param is spent, whether the group list is cached, uncached, or the link mounts the tab mid-navigation.
Creating a drawer is not only a drawer: the API grants the caller's personal group manage access on it, which is a rule, and creates that personal group when they had none. Invalidating only the drawer list left the Rules table missing the new rule whenever the rule modal was cancelled after making a drawer. Also names the lock's reason for a group with no owner, which had shown a lock and no delete with nothing explaining either.
Permission editing has always been drawer-scoped: legacy reaches Permissions::edit from a drawer that already exists, so it never needed a drawer picker, let alone a way to make one. Creating an empty drawer to share it, then leaving to fill it, is not the flow. Takes with it the invalidation chain addDrawer forced on us: creating a drawer also creates a grant, and sometimes a group, so three lists went stale at once. Nothing on this page creates drawers now, so none of that reasoning has to be carried. Creating a group mid-rule stays. A group is a permission concept with no home outside this flow, and All Drawers already creates drawers.
The chip spanned its whole column and carried a neutral border. It now sits at its content's width, borders in its level's dot color, and rests on a 10% wash of that color. Tailwind's alpha modifier does the mixing: `bg-green-500/10` composites onto whatever surface is behind it, so one class covers light and dark without a CSS variable to plumb through. Both Rules tabs render the same component now, pending spinner included, so they cannot drift. Level colors move to helpers, which the chip and the select both reach without either owning the other.
Mirrors the API: an admin reaches every drawer and may delete a grant for a group they do not own. A manager still sees Delete only on their own groups, with No Permissions as the revoke everywhere else.
The filters live in the query string rather than in component state, so a link can carry them. A drawer's page uses that: its Edit Permissions button now opens the Rules tab filtered to that drawer, in the new UI, instead of the legacy per-drawer permission editor. - ?drawer=5,8 and ?group=3 narrow the table, ?owner=mine|others cuts by whose group a rule reaches - a default filter stays out of the URL, matching how ?tab already works - the pickers offer only the drawers and groups the rules actually reach, since a pick with no rules would filter the table to nothing, and an admin's drawer list runs to the whole instance The Groups tab's row reveal moves from ?group to ?revealGroup. Both tabs share one query string, so a tab switch carried the rules filter into the reveal and jumped to a group nobody asked for.
- the drawer and group pickers are comboboxes now, so a long list is typed at rather than scrolled. Their options carry numeric ids, which reka's own filter would match against instead of the label, so the filtering happens here behind `ignoreFilter` - Group owner reads Me | Others | All and opens on Me: the rules for your own groups are the ones you can act on fully, and the rest is a widening - filters, search, and Create Rule share one wrapping row. The explainer text goes: the page header already says what this is. On a narrow screen the pickers split the width and search takes its own line - `optionClass` lets the segmented control match the height of the form controls beside it, so all three labels sit on one line A drawer's Edit Permissions link asks for `owner=all`, since the rules worth seeing there include the ones for other people's groups.
Whose group it is decides what you can do with its rules, so the picker tells the two apart with a heading and a rule between them. MultiSelect takes sections rather than a flat option list. A list that needs no heading, like the drawers, is one section without a label, so there is a single path through the template either way. A section whose options are all filtered out drops its heading with them.
There was a problem hiding this comment.
Pull request overview
This PR introduces a consolidated drawer-sharing management UI at /drawers/:drawerId/manage, refactors shared permissions UI/querying to support both drawer managers and instance admins, and removes the feature-flag gating for these pages.
Changes:
- Adds the drawer sharing “Group Access” table (create groups, set access, manage members/entries, revoke access) with new drawer grant/group query/mutation plumbing and e2e coverage.
- Refactors permissions UI into shared
PermissionSelect/PermissionChipand a sharedpermissionLevelsQuery, updating admin permissions screens accordingly. - Removes
config.features.*feature flags and updates routing/navigation plus mock-server endpoints/data to match the new flows.
Reviewed changes
Copilot reviewed 61 out of 63 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/e2e/drawer-sharing.spec.ts | Adds e2e coverage for drawer sharing UI flows |
| src/types/PermissionsTypes.ts | Extends/adjusts permission/group types for drawer grants |
| src/types/index.ts | Removes feature-flag fields from AppConfig |
| src/router.ts | Makes admin permissions + drawer manage routes always available; adds drawerId route param |
| src/queries/permissionLevelsQuery.ts | Adds shared permission levels query |
| src/pages/DrawerViewPage/DrawerViewPage.vue | Updates “Edit Permissions” link to new drawer manage route |
| src/pages/DrawerManagementPage/toGroupTypeOptions.ts | Maps group type catalog to select options (adminOnly disabling) |
| src/pages/DrawerManagementPage/toGroupTypeOptions.test.ts | Tests group type option mapping |
| src/pages/DrawerManagementPage/toGroupSummary.ts | Builds “type • count members/entries” summary text |
| src/pages/DrawerManagementPage/toGroupSummary.test.ts | Tests group summary formatting |
| src/pages/DrawerManagementPage/toDrawerTitle.ts | Shared drawer title fallback formatting |
| src/pages/DrawerManagementPage/toDrawerTitle.test.ts | Tests drawer title fallback logic |
| src/pages/DrawerManagementPage/RulesTabContent.vue | Removes placeholder rules tab content |
| src/pages/DrawerManagementPage/GroupsTableColumns.tsx | Removes legacy drawer groups table column definitions |
| src/pages/DrawerManagementPage/GroupsTabContent.vue | Removes legacy groups tab UI |
| src/pages/DrawerManagementPage/GroupMemberManager.vue | Adds success/error toasts for member removal |
| src/pages/DrawerManagementPage/GroupFormModal.vue | Removes legacy group create/edit modal |
| src/pages/DrawerManagementPage/GroupEntriesTableRow.vue | Adds success/error toasts for entry edit/remove |
| src/pages/DrawerManagementPage/GroupAccessTableColumns.tsx | Implements columns/actions for the new Group Access table |
| src/pages/DrawerManagementPage/GroupAccessTable.vue | Implements the new drawer sharing table (groups + grants join) |
| src/pages/DrawerManagementPage/DrawersTableColumns.tsx | Removes legacy manageable drawers table columns |
| src/pages/DrawerManagementPage/DrawersTabContent.vue | Removes legacy drawers tab UI |
| src/pages/DrawerManagementPage/DrawerManagementPage.vue | Replaces tabbed management page with per-drawer sharing page |
| src/pages/DrawerManagementPage/drawerGroupQueries.ts | Refactors group mutations; adds shared invalidation for groups+grants |
| src/pages/DrawerManagementPage/drawerGrantQueries.ts | Adds drawer grant query + create/update/delete mutations |
| src/pages/DrawerManagementPage/buildGroupAccessRows.ts | Builds table rows by joining grants, groups, types, and levels |
| src/pages/DrawerManagementPage/buildGroupAccessRows.test.ts | Tests row-building behavior and sorting |
| src/pages/DrawerManagementPage/AddGroupRow.vue | Adds inline “create group + grant” row flow |
| src/pages/DrawerManagementPage/AddGroupMemberRow.vue | Adds toasts and retry-friendly behavior for adding members |
| src/pages/DrawerManagementPage/AddGroupEntryRow.vue | Adds toasts and retry-friendly behavior for adding entries |
| src/pages/AllDrawersPage/AllDrawersPage.vue | Removes feature-flagged “Manage Drawers” button |
| src/pages/AdminPermissionsPage/RulesTableColumns.tsx | Switches to shared permission UI + updates deep-link param |
| src/pages/AdminPermissionsPage/RulesTabContent.vue | Uses shared permission levels query + shared option builder |
| src/pages/AdminPermissionsPage/ruleQueries.ts | Removes page-local permissionLevelsQuery |
| src/pages/AdminPermissionsPage/RuleFormModal.vue | Uses shared PermissionSelect/query + updates deep-link param |
| src/pages/AdminPermissionsPage/permissionDotClass.ts | Removes legacy permission-dot color helper |
| src/pages/AdminPermissionsPage/GroupsTabContent.vue | Updates deep-link param handling to revealGroup |
| src/helpers/permissionLevelColors.ts | Adds shared permission dot/chip color mapping helpers |
| src/helpers/permissionLevelColors.test.ts | Tests permission color helpers |
| src/config.ts | Removes feature-flag parsing from config |
| src/components/SegmentedControl/SegmentedControl.vue | Adds optionClass support for segmented options |
| src/components/PermissionSelect/PermissionSelect.vue | Uses shared permission color helper |
| src/components/PermissionSelect/buildPermissionOptions.ts | Includes level 0 options (no longer filters it out) |
| src/components/PermissionSelect/buildPermissionOptions.test.ts | Tests permission option building |
| src/components/PermissionChip/PermissionChip.vue | Adds shared PermissionChip component |
| src/components/InputGroup/InputGroup.vue | Adds placeholder prop support |
| src/components/AppMenu/PagesNavSection.vue | Formatting/indentation cleanup |
| src/components/AppMenu/AppMenu.vue | Removes feature-flagged “Manage Drawers” menu item |
| src/components/AppMenu/AdminNavSection.vue | Always links to admin permissions page; removes feature-flag gating |
| src/components/AdminSidebar/AdminSidebar.vue | Always shows admin permissions nav item |
| src/api/fetchers.ts | Updates permission levels endpoint; adds drawer grants API; renames manageable drawers endpoint/shape; renames group update to rename |
| mock-server/types.ts | Adds drawer group/grant types and user fields |
| mock-server/server.ts | Registers new permissions + drawerPermissions routes |
| mock-server/routes/permissions.ts | Adds mock permissions/permissionLevels endpoint |
| mock-server/routes/drawerPermissions.ts | Adds full mock drawer permissions/groups/grants API surface |
| mock-server/db/users.ts | Types user seeds; supports dynamic user creation and reset |
| mock-server/db/permissionLevels.ts | Adds seeded permission levels catalog for mocks |
| mock-server/db/index.ts | Adds drawerGroups and drawerGrants tables to mock DB |
| mock-server/db/drawers.ts | Adds a second seeded drawer; updates id counter reset |
| mock-server/db/drawerGroups.ts | Adds seeded drawer groups + entry management helpers |
| mock-server/db/drawerGrants.ts | Adds seeded drawer grants + helpers |
| bin/deploy-dev.sh | Stops writing removed feature flags into .env |
| .env.example | Removes feature flag entries |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const option = permissionOptions.value.find( | ||
| (candidate) => candidate.id === levelId | ||
| ); | ||
| return option?.label ?? ""; |
cmcfadden
approved these changes
Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
/drawers/{id}/managewhere a drawer's manager creates groups, sets their access level, manages members and entries, and revokes access, all in one Group Access table.PermissionSelectandPermissionChipcomponents.CleanShot.2026-07-17.at.12.22.10.mp4