Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
144
planned
1,362
shipped

Motir · Work items

MOTIR-2258Done

Permission-gated UI — hide what the actor cannot reach, disable what they cannot use

Make the interface reflect the actor's permissions instead of showing every surface and refusing the write. A member with no settings permissions does not see the Project settings entries they cannot use; a role without work_item:transition does not get a status control that fails on click. This is the half of the epic the user asked for by name, and it is the half that needs a recorded decision before any pixel moves.

The rule this story decides and records

design/projects/design-notes.md § Gating affordances (6.4.6) carries a PM directive dated 2026-06-09 that already settles part of this — in-place controls stay visible-but-disabled with a tooltip, navigation-to-an-edit-surface is hidden and the route is guarded — and it also states "a non-admin sees Members + Access read-only", which is precisely the behaviour this story changes. That section is AMENDED here, on the record, with its date and its reason — it is not silently contradicted, and the amendment is a deliverable of this story, not a side effect of it. The rule the amendment lands, consistent with the field (a surface is hidden when the actor has no path to obtain access and no need to know it exists; it is disabled with an explanation when they can see the surface and the control teaches them the action exists):

  1. HIDE an entry point — a nav entry, a menu item, a settings section, a command-palette action — whose destination the actor cannot use at all.
  2. DISABLE with a tooltip an in-place control on a surface the actor CAN see, so they learn the action exists and why it is refused. The shipped Create button, board drag and inline field pickers keep this treatment unchanged.
  3. GUARD every hidden destination on the server. Hiding is presentation, never enforcement — a direct navigation to a hidden route renders the no-access state or redirects, exactly as /issues/[key]/edit already does for a read-only actor.

The shape

  • The actor's permission SET reaches the client once per request. app/(authed)/_components/ProjectAccessProvider.tsx carries two booleans today; it carries the resolved set and a can(permission) reader after this story. The layout resolves it in the same round trip it already spends on getSettingsCapabilities, which projectAccessService.getPermissionsDTO (shipped by the permission model) already generalises.
  • Every surface registry gates on a named permission. lib/settings/projectSettingsNav.ts's per-entry access predicate — every entry but automation is browse-gated today — names the domain permission from the per-domain split instead. The command palette reads the same registry, so it cannot drift. The project nav and the work-item action menus gate the same way.
  • A settings AREA with nothing visible in it is itself hidden. An actor whose every settings entry filters away does not get an empty rail — the Project settings door disappears, which is the user's stated case.
  • Empty and refused states are designed, not incidental. A hidden surface reached directly, and a group whose entries all filtered away, both have a drawn state.

Scope boundary

In: the visibility decision and the amendment to the design notes; the permission set delivered to the client; the settings-nav, command-palette, project-nav and action-menu gates; the settings-area door; the no-access and empty states; the story's two test subtasks including the acceptance video.

Out: the permission catalog and the per-domain permissions themselves — both are prerequisites, not deliverables here; the SERVICE- and ROUTE-level write gates, which are the per-domain split's and the member-facing sweep's job and which this story assumes and re-verifies rather than adds (the PAGE-level render guard on a now-hidden destination IS in scope — it is rule 3 above); custom roles, which are MOTIR-2257's — so the live walkthrough of a role holding one administrative domain and not another is deferred to that story, and is asserted here at the integration level over a synthetic permission set instead.

Verification recipe

  • Pull the story branch, pnpm install, pnpm dev.
  • Sign in as a project member on the motir project: the sidebar's Project settings door is absent, and ⌘K offers no settings deep links. Navigating directly to /settings/project/members renders the no-access state rather than a read-only form.
  • Sign in as a project viewer: the same doors are gone, and the project-nav entries whose destinations refuse a viewer outright are gone too — while the in-place treatment is unchanged from what shipped: the Create button is visible-but-disabled with its tooltip, the board is read-only with its banner, and the issue-detail Edit link is absent.
  • Sign in as a project admin: every settings entry is present and reachable, unchanged — nothing an admin could do before was taken away.
  • Confirm the per-domain case at the integration level: fed a synthetic set holding board:configure and not member:manage, the settings registry yields Board and omits Members. The live custom-role walkthrough of the same case belongs to MOTIR-2257.
  • pnpm test:e2e --grep permission-gated-ui drives the whole flow and records the acceptance video.