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

1.3 Projects

A workspace contains projects. Each project has a name, slug, and identifier (e.g., PROD). A workspace holds many projects (the project switcher lists them); the app scopes to one active project at a time at the UI level, and the non-unique workspaceId FK accommodates as many projects as a workspace needs — no per-workspace cap. Cross-project views that span projects at once (unified search / reporting) belong to the Search, reporting & admin epic (Epic 6).

Prerequisites: Story 1.2 (Workspaces) must be complete — project FKs against Workspace with onDelete: Cascade, the project RLS policies key off the same app.workspace_id session GUC that 1.2.3 established, and the active-project selection rides on the existing WorkspaceMembership row. Story 1.0.5 (Design system) must be complete before 1.3.3 (mockups) and 1.3.4 (UI) — those compose the canonical Button / Input / Card / Modal / Popover primitives (Popover shipped in 1.2.6).

Verification

  • project table: id (cuid), workspaceId (FK, onDelete: Cascade), name, slug (workspace-unique), identifier (3-5 chars uppercase, workspace-unique), lastWorkItemNumber (int default 0 — the per-project key counter), createdAt, updatedAt, archivedAt (nullable — soft-delete/archive, never a hard delete that would orphan work-item history in 1.4+).
  • Project identifier is workspace-unique and used as the prefix for work-item keys (e.g., PROD-42). Auto-generated from the name (uppercased, alphanumeric, 3-5 chars) with a numeric collision suffix; the user can override at creation.
  • Work-item-key allocation is gap-free and per-project: a repository method increments project.lastWorkItemNumber via UPDATE … RETURNING inside the caller's transaction (NOT a Postgres SEQUENCE — sequences are per-DB-object, leak on rollback, and would need one per project). Story 1.4 calls this when inserting a work item.
  • Active project stored per-workspace-member on WorkspaceMembership.activeProjectId (nullable FK, onDelete: SetNull) — determines what the member sees on landing. Switching projects updates this, mirroring the workspace_id cookie pattern from 1.2.6.
  • Postgres RLS on project: a row is visible/writable only when its workspaceId matches the active app.workspace_id GUC — the same structural gate 1.2.3 applied to workspace-scoped tables. Cross-workspace access is structurally impossible at the DB layer, not just the app layer.
  • Creating a project requires active workspace membership; archiving/deleting requires the typed-name double-confirmation modal (same pattern as workspace delete in 1.2.6).
  • Project creation flow: a modal with name + auto-generated identifier (overridable); on success the new project becomes the member's active project. If the member has no projects in the active workspace, a "Create your first project" empty state appears instead of a project view.
  • Project switcher in the top-nav (composed alongside the workspace switcher from 1.2.6): lists the workspace's projects with a check on the active one + a "Create project" entry. Selecting sets activeProjectId and re-renders.
  • 4-layer rule respected (per motir-core/CLAUDE.md): route/Server-Action → service → repository → Prisma; writes go through repo methods requiring tx; services own transactions + DTO mapping. All quality gates green; multi-tenant isolation proven by E2E + direct-DB RLS test (1.3.6).