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-2282Done

The Roles & permissions screens — the role list a project admin drills into to see what each role can do

Ship the surface that makes the permission model real to a person: Project settings → Access → Roles & permissions — a role list you drill into to read what one role can do.

⚠️ RE-PLANNED 2026-08-07, against the design that had since merged. This story was authored on 2026-08-06, a day before its own design child MOTIR-2259 landed. That asset answered two questions this story had already answered differently, and it is done and on main — so it wins (rung 2). What changed is recorded in What the design settled below; the superseded gate is struck on the record rather than deleted, with its evidence, so a later reader can see it was amended rather than dropped.

Why this is its own story (re-sliced 2026-08-06, Yue)

This was originally the tail of the model story, which bundled the catalog, the enforcement seam AND the page so the story had a surface a person could accept. That bundling produced a straddle that surfaced three separate times: the page needs work that comes AFTER the per-domain split, while the split needs only the model half — so the model story kept having to sit on both sides of a sibling.

The straddle was the signal the slice was wrong. The epic's own shape is already layered — MOTIR-2256 is model-only, MOTIR-2258 is UI-only — so the layers are the honest boundary here, and the page is a vertical slice in its own right: a project admin opens a page and reads what their roles mean, verifiable on its own.

What the design settled — and what it therefore changes about this story

design/projects/roles-permissions.mock.html + its section in design/projects/design-notes.md shipped in commit 1273ff32. Two decisions in it are load-bearing here:

1. The structure is a DRILL-DOWN, not a matrix. Two screens: a role LIST (one row per role, carrying its name, its purpose, N of 28 permissions, and how many people hold it) and a role DETAIL (that role's permissions at full width, under domain headings). The notes are explicit that "an earlier revision put permissions on ROWS and roles on COLUMNS… could not survive five, which is why it was replaced" — a matrix's width is consumed by the very thing custom roles adds. So this story ships two screens and a nested route, not one grid.

2. The screens draw the model AS IT WILL BE, not how far enforcement has got. The notes reject all three alternatives by name — hiding the unwired keys, marking them "Not yet enforced", and showing them held by nobody — because "a settings page describes the product, not its migration." The asset draws 28 role-gated rows with the eight member-facing keys already marked, taking the grants MOTIR-2347 decides.

The gate this story ships behind: PLANNED_PERMISSIONS must be empty. STRUCK 2026-08-07. It was unsatisfiable inside this story and it was aimed at the wrong thing. lib/permissions/catalog.ts on origin/main holds 8 planned keys and its own comment states emptying the array "takes BOTH MOTIR-2256 … and MOTIR-2291 … neither story empties it alone" — and 2291 is not done. More to the point, the design makes the emptiness beside the point: the page shows the whole role-gated model regardless of wiring progress. What replaces it is a contract the page can actually be held to — the grid's row set is exactly ROLE_GATED_PERMISSIONS and each role's marks are exactly BUILTIN_ROLE_PERMISSIONS[role] — plus the real dependency, wired as an edge rather than asserted in a test: this story is now blocked_by MOTIR-2291, because the marks the design draws come from MOTIR-2347's decision and only exist in the role sets once MOTIR-2349 lands them. The design says so itself: "this asset draws MOTIR-2347's recommendation as settled, and that card has not run… it is a ratification these screens depend on."

Scope

In: the settings screens (list + detail) and their rail entry; the widened role-catalog read they consume; the i18n; the story's Vitest gate and its Playwright E2E with the acceptance video.

Out: the catalog, the roles-as-sets model and the base service read (all MOTIR-2255, done); the enforcement wiring (MOTIR-2256, done); deciding and wiring the eight member-facing keys (MOTIR-2291 — this story consumes its result and adds nothing to it); custom roles, the Create role page and the role editor (MOTIR-2257 — the mock's third panel is that story's, not this one's); gating which surfaces a role can SEE (MOTIR-2258).

Verification recipe

  • Pull the branch, pnpm install, pnpm dev; sign in as the project manager on motir.
  • Open Project settings → Access → Roles & permissions by CLICKING the rail entry in the Access group — not by typing the URL. The door is part of what is being verified.
  • The role list shows the three built-in roles, each with its Built-in chip, its one-line purpose, its N of M permissions count and how many people hold it.
  • Drill into a role. Its permissions render at full width, grouped by domain, each with a human label and description — no raw keys, and no "coming soon" state anywhere. Return via the ← All roles link.
  • Sign in as a project member: both screens, same content, read-only — no Create role, no Edit, no Delete.
  • As a user with no browse access, a direct navigation to either screen renders the shipped no-access panel.
  • As a project viewer, confirm nothing about the shipped affordances changed.

Acceptance criteria

  • The role list renders at /settings/project/roles and a role's detail at its own nested route, both reachable from the settings rail's Access group, built to design/projects/roles-permissions.mock.html.
  • The rows the screens draw are exactly ROLE_GATED_PERMISSIONS, and each role's marks are exactly BUILTIN_ROLE_PERMISSIONS[role] — asserted against those constants, never against a literal count, so the assertion survives the set growing from 20 keys to 28.
  • No permission the screens draw renders a raw catalog key, and no row carries an enforcement or migration marker of any kind.
  • Every string is in both messages/en.json and messages/zh.json; the catalog-parity test passes.
  • Zero axe violations on both screens; both are reachable and readable by keyboard.
  • The route↔registry totality test (tests/settings/projectSettingsNav.test.ts) passes, with the nested detail route accounted for deliberately rather than by weakening the assertion.
  • Per-file coverage on the new pages, components and the widened read meets the ≥90% floor.
  • The E2E drives the recipe above, including the drill-down and the return, and publishes a chaptered acceptance video.