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
145
planned
1,361
shipped

Motir · Work items

MOTIR-2263Done

The Roles & permissions screens — the role list, the role detail it drills into, their rail entry, and the i18n

Build the screens the design card drew: Project settings → Access → Roles & permissions, a role list that drills into a role detail, read-only. This is the story's feature-finishing card — the point at which the permission model becomes something a person can open and check.

⚠️ RE-SCOPED 2026-08-07. This card was authored on 2026-08-05 describing a single-screen grid"one card per role, its permissions grouped by catalog domain" — and its first criterion cited "panel 1" of the mock as the reference for that. The design landed on 2026-08-07 (commit 1273ff32) with a drill-down instead, and design/projects/design-notes.md records why in its own words: "an earlier revision put permissions on ROWS and roles on COLUMNS. It read well at three roles and could not survive five, which is why it was replaced." Panel 1 is now the role LIST; a role's permission set is the DETAIL screen. Built to the old text, this card would have shipped the structure the design explicitly replaced. See the story's re-plan note.

The shape

  • The list screenapp/(authed)/settings/project/roles/page.tsx, a server component reading projectAccessService.getRoleCatalog for the active project and gating on the same browse capability the sibling settings pages use. One row per role carrying its icon tile, name, Built-in chip, one-line purpose, N of M permissions, its member count, and a chevron into the detail. The informational note above the list explains that the built-in roles cannot be changed.
  • The detail screen — a nested route under the same segment, rendering one role's permissions at full width under their domain headings, each row a human label with its description beside it. It carries both the inherited breadcrumb and an explicit ← All roles link: the design notes call this deliberate — "the inherited crumb trail is orientation, the back link is the control, and a drill-down needs a control rather than a place to read where you are."
  • ⚠️ The nested route needs a deliberate answer to the nav-totality test, not a weakened assertion. tests/settings/projectSettingsNav.test.ts enumerates the filesystem and asserts that every app/(authed)/settings/project/**/page.tsx route has EXACTLY one entry in lib/settings/projectSettingsNav.ts and vice versa. A drill-down adds a second page.tsx under roles/ that must NOT get its own rail entry — it is not a destination in the rail. Decide how the registry expresses that (a nested/child route the enumeration recognises as belonging to its parent entry is the shape to reach for) and make the totality assertion still mean what it says: the point is that no reachable settings destination lacks a door, not that every file has a row. Do not relax the test to make a second file pass.
  • The rail entry is not optional and not separable. A roles entry in the Access group, next to members, with an icon from the lucide set the registry already imports and an access predicate of browse — matching every other entry in the registry today. Changing that predicate to a permission is the permission-gated UI story's job, not this card's.
  • i18n both catalogs — every string in messages/en.json and messages/zh.json, including the rail label, both screen headings, the role names and purposes, the built-in note, and the N of M permissions / member-count strings with their plural forms.

Scope boundary

In: the two screens, their components, the rail entry, the nav-totality answer, the i18n, and their unit tests.

Out: the widened read they consume (MOTIR-2439 — this card renders its DTO and does not change lib/dto, lib/mappers, lib/repositories or lib/services); the Create role button and page, the Edit / Delete affordances and the provenance chip, all of which the mock draws for custom roles; deciding or wiring the eight member-facing keys (MOTIR-2291).

Acceptance criteria

  • Navigating to /settings/project/roles as a project admin renders the role list: the three built-in roles, each with its Built-in chip, its purpose, its N of M permissions count and its member count, matching the list panel of design/projects/roles-permissions.mock.html.
  • Activating a role row opens that role's detail screen at its own nested route, rendering its permissions grouped by domain with human labels and descriptions; the ← All roles link returns to the list, and the breadcrumb is present alongside it.
  • The rows both screens draw come from the role-gated set the widened read returns, and each role's marks come from that role's set — the components derive neither from a literal count nor from importing the catalog directly.
  • The Access group of the settings rail shows a Roles & permissions entry that opens the list, and the command palette offers it as a deep link — both from the single registry entry, not from a second list. The detail route gets no rail entry of its own.
  • tests/settings/projectSettingsNav.test.ts passes with its route↔registry totality assertion still asserting that every reachable settings destination has exactly one door — not relaxed to admit an unmapped file.
  • A project member opening either screen sees the same content, with no write affordance rendered; a user with no browse access to the project gets the shipped no-access state on either route rather than an empty page or a crash.
  • No permission label or description renders a raw key — a missing i18n string fails the render in test rather than showing permissions.work_item:edit.
  • Every string is present in both messages/en.json and messages/zh.json; the catalog-parity test passes.
  • Both screens render with zero axe violations; the list rows and the detail are reachable and readable by keyboard, and the row's activation is a real control rather than a click handler on a div.
  • Colour is --el-* only and shape is the element-semantic tokens, so the display-style swap reshapes both screens.
  • Per-file coverage on the new pages and components meets the ≥90% floor.

Context refs

  • design/projects/roles-permissions.mock.html and its Roles & permissions section in design/projects/design-notes.md — the reference these screens are built to; read the SOURCE and every panel, and note which panels belong to custom roles rather than to this card.
  • lib/settings/projectSettingsNav.ts — the registry the entry is added to; visibleSettingsNav and the group ordering.
  • tests/settings/projectSettingsNav.test.ts — the route↔registry totality guard the nested route must satisfy honestly.
  • app/(authed)/settings/project/members/page.tsx and its _components/ProjectMembersSettings.tsx — the sibling Access-group page, for the page shell and gating shape.
  • The widened readgetRoleCatalog, the data source these screens consume.
  • The design card — the asset this builds to.