Skip to content

Drawer sharing UI#587

Open
jxjj wants to merge 29 commits into
developfrom
feat/20260714-drawer-rule-management
Open

Drawer sharing UI#587
jxjj wants to merge 29 commits into
developfrom
feat/20260714-drawer-rule-management

Conversation

@jxjj

@jxjj jxjj commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
  • Adds a sharing page at /drawers/{id}/manage where a drawer's manager creates groups, sets their access level, manages members and entries, and revokes access, all in one Group Access table.
  • Unifies the admin permissions page into Groups and Rules tabs with searchable, URL-driven filters, and extracts shared PermissionSelect and PermissionChip components.
  • Removes the feature flag that gated drawer management.
CleanShot.2026-07-17.at.12.22.10.mp4

jxjj added 29 commits July 15, 2026 21:53
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.
@jxjj
jxjj requested a review from Copilot July 17, 2026 17:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 / PermissionChip and a shared permissionLevelsQuery, 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 ?? "";
@jxjj jxjj self-assigned this Jul 17, 2026
@jxjj
jxjj marked this pull request as ready for review July 17, 2026 17:26
@jxjj
jxjj requested a review from cmcfadden July 17, 2026 17:26
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