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

The settings registry gates on named permissions — the rail, the ⌘K deep links, and the area door that disappears when nothing is behind it

This is the card the parent story is named for: a member stops seeing settings sections they cannot change anything in, and when none is left, the Project settings door goes with them.

lib/settings/projectSettingsNav.ts is already the single registry three surfaces read — the rail, the ⌘K deep links, and the route↔registry totality test — so the gate does not need building, only re-pointing. Today every entry but automation gates on browse, and the module's own comment records the intent it is time to retire: "a member VIEWS every section". The entry for Roles says it in as many words: "Turning that into a permission predicate is MOTIR-2258's job, not this card's."

The approach

  • The predicate takes the actor's permission SET. SettingsNavCapabilities is replaced by the resolved set from the client-set card; each entry names the PermissionKey its destination requires.
  • Every pairing is VERIFIED, not assigned by name similarity. For each entry, open its destination and read the key its own server gate asserts — board/page.tsx and boardsService for Board, projectAiSettingsService for AI planning, automationRulesService for Rules, and so on — and name that key. Where a destination gates on more than one key, the entry names the one that makes the page useful at all, and the notes say which and why. Two entries need a judgement rather than a lookup and must state it on the card: Details, whose page is the project-level acts that belong to no domain, and Roles, whose page is a read-only view of what every role can do — decide each against that page's own gate and record the reasoning in the PR body.
  • The rail renders only non-empty groups. groupSettingsNav already drops empty groups; the card's job is to prove it, because a heading with nothing under it reads as a loading failure (design panel 2).
  • The AREA door. The bottom-nav Settings row deep-links to /settings/project when a project is active. It is hidden when the actor's visible settings set is empty. With no active project the row still points at workspace settings — that path is untouched, because workspace settings are governed by the workspace role this epic does not change.
  • The palette follows the registry. Its settings block is a visibleSettingsNav call; update the call site to pass the set and nothing else. The palette's OWN action entries — create issue, plan with AI, plan sprints — belong to the nav-and-palette card.

Scope boundary

In: lib/settings/projectSettingsNav.ts, app/(authed)/_components/SidebarNav.tsx, the visibleSettingsNav call site in app/(authed)/_components/AppCommandPalette.tsx, the layout prop that feeds them, their i18n keys, and their tests.

Out: the destination PAGES' own render guards — a hidden entry whose page still renders a read-only form is the destination-guard card's to fix, and this card must not touch app/(authed)/settings/project/**/page.tsx; the palette's non-settings actions and the project nav rows (the nav-and-palette card); every service and route gate, which this story does not change.

Acceptance criteria

  • Every entry in PROJECT_SETTINGS_NAV names a PermissionKey, and a grep of lib/settings/projectSettingsNav.ts for canBrowse or canManage returns nothing.
  • A test walks PROJECT_SETTINGS_NAV totally — a new entry with no declared key fails to compile or fails the test — and asserts each entry's key against the key that destination's own gate asserts.
  • Fed a set holding project:browse and board:configure and no other administrative key, visibleSettingsNav yields Board and omits Members; fed a built-in member's set it yields nothing.
  • groupSettingsNav emits no group whose entries all filtered away, asserted directly.
  • With an active project and an empty visible settings set, SidebarNav renders no Project settings row; with no active project the row still targets /settings/workspace, unchanged.
  • AppCommandPalette offers no settings deep link an actor cannot use, and holds no second copy of the settings list — its block is built from visibleSettingsNav.
  • The shipped route↔registry totality test stays green and still pairs every on-disk settings/project/**/page.tsx 1:1 with PROJECT_SETTINGS_ROUTE_PATHS, nested routes included.
  • The rail and the bottom nav match panels 1 and 2 of design/projects/permission-gated-ui.mock.html.
  • An admin's rail is byte-for-byte the rail that ships today — every entry present, in the same order and groups.
  • Every new en message key has its zh twin.
  • The changed files meet the repo's per-file coverage floor.

Context refs

  • lib/settings/projectSettingsNav.ts — the registry, its access predicate, visibleSettingsNav, groupSettingsNav, and the Roles entry's note deferring this work here.
  • app/(authed)/_components/SidebarNav.tsx — the settings-area branch (~line 183) and the bottom-nav Settings row (~line 320).
  • app/(authed)/_components/AppCommandPalette.tsx — the visibleSettingsNav call (~line 282).
  • tests/settings/projectSettingsNav.test.ts — the totality guard that must stay green.
  • lib/permissions/catalog.ts — the key set each entry draws from.
  • The client-set card — the provider this card reads the set from.
  • The design card — panels 1 and 2 are this card's reference.