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

The project nav and the ⌘K actions gate on the permission their destination needs — through one shared map, not two lists

The settings area is not the only place the shell offers rooms an actor cannot enter. The project nav lists ten destinations and the command palette lists its own set of navigations and actions, and both are gated today on nothing more than "is there an active project". A viewer is offered Plans, whose page answers with the no-access state; the palette offers deep links to the same.

The approach

  • One shared map, consumed twice. The sidebar's project rows and the palette's navigation entries are two independent arrays that already overlap, and gating them separately guarantees they drift — the same failure the settings registry exists to prevent one area over. Introduce a single exported map from nav destination to its required PermissionKey (or an explicit browse-only marker), and have both surfaces resolve through it. This is a gating registry, not a rewrite of either nav: the rows, their icons, their order and their labels stay where they are.
  • Every pairing is READ off the destination, not guessed. For each row, open the destination and grep the key its own page or service asserts — plansService for Plans, the triage page's own gate for Triage, estimationService / the reports pages for Reports, and so on. A destination with no gate beyond browse is marked browse-only and stays visible; that is a legitimate answer and must be recorded rather than back-filled with a plausible key.
  • Hide versus disable is the decision's call, not this card's. Follow the treatment table from the hide-versus-disable decision — this card CONSUMES that table, which is why it waits on it. Note in particular that the 2026-06-09 directive it amends puts the palette's Create action in the visible-but-disabled family alongside the Create button and the C shortcut — so this card does not hide it, and any action the table marks DISABLE keeps its shipped treatment.
  • Panel 4 of the design asset decides what happens to the gap a removed row leaves and whether section separators survive.

Scope boundary

In: the shared nav-permission map and its tests, the project-nav rows in app/(authed)/_components/SidebarNav.tsx, and the palette's own navigation and action entries in app/(authed)/_components/AppCommandPalette.tsx.

Out: the settings rail, the settings deep-link block and the settings-area door, all of which the settings-registry card owns and this card must not re-decide; the destination pages themselves (the destination-guard card); the work-item action menus and in-place controls (the affordance sweep); every service and route gate.

Acceptance criteria

  • A single exported map pairs every project-nav destination and every palette navigation entry with its required PermissionKey or an explicit browse-only marker; both SidebarNav and AppCommandPalette resolve through it and neither holds a second gating list.
  • The map is TOTAL over both surfaces: a test enumerates the rows each surface renders and fails if one resolves to no entry, so a row added later cannot ship ungated by omission.
  • Every non-browse-only pairing is justified in the PR body by the grep that produced it — the file and the line where that destination asserts the key.
  • A row whose destination refuses the actor outright is not rendered; a row whose destination merely refuses a WRITE stays, per the decision's treatment table.
  • An action the treatment table marks DISABLE — the palette's Create entry among them — keeps its shipped visible-but-disabled treatment; a test asserts it is still offered to a viewer.
  • A project admin sees the same project nav and the same palette entries, in the same order, as at the branch's own merge base — compared against that baseline before the PR lands.
  • The rendered nav matches panel 4 of design/projects/permission-gated-ui.mock.html, including what happens to the gap a removed row leaves.
  • Every new en message key has its zh twin.
  • The changed files meet the repo's per-file coverage floor.

Context refs

  • app/(authed)/_components/SidebarNav.tsx — the project-nav array (~line 212) and the Resume-onboarding row that leads it.
  • app/(authed)/_components/AppCommandPalette.tsx — the palette's own entries: create-issue, plan-with-ai, backlog-plan-sprints, nav-*, nav-filters, resume-onboarding.
  • app/(authed)/plans/page.tsx and app/(authed)/triage/page.tsx — two destinations that already refuse an actor, and therefore already tell you their key.
  • lib/permissions/builtinRoles.ts — what viewer and member actually hold, which is what decides whether a row disappears for a built-in role at all.
  • lib/settings/projectSettingsNav.ts — the shape this card's map deliberately mirrors.
  • The hide-versus-disable decision — the treatment table this card resolves every row against.
  • The design card — panel 4 is this card's reference.