MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-408

6.12 Public projects (open project management)

Done
Description

Make a project public — open for read-only VIEW to ANYONE on the web (no sign-in), ACROSS orgs and workspaces — where a viewer can change NOTHING except submit a bug / feature request (into the 6.11 Triage), upvote an existing request, and comment on it, and those three writes REQUIRE sign-in. This is the "open source project management" / public-feedback-portal posture: a fully public, crawlable (SEO/GEO) roadmap + a sign-in-to-act intake. A pure motir-core, per-project capability — no AI boundary, no forward dependency.

Model revision (Yue, 2026-06-14): READ is fully public / anonymous + SEO/GEO-optimised; only the three WRITES require sign-in (the GitHub / Canny standard). Supersedes the earlier "account-required, not anonymous" framing for READ; anonymous writes stay out of scope.

The model (locked — see the module header for the full rationale + the verified mirror):

  • public is a 4th ProjectAccessLevel, extending 6.4. Today open / limited / private (6.4, DONE); 6.12 adds public. The openness ladder is public > open > limited > private. public is the ONLY level that crosses the org boundary for READ — 6.4’s canBrowse returns true for ANYONE, INCLUDING no session at all, on a public project (bypassing the org/workspace gate for READ on public projects only); every other level stays org/workspace-bounded and the 404-not-403 posture for non-public projects is untouched.
  • READ anonymous + crawlable; WRITE needs sign-in. Anyone (logged out included) can VIEW a public project, and the page is server-rendered + crawlable (SEO/GEO). The three writes (submit / upvote / comment) require a signed-in account, so each still carries a real account for attribution + rate-limiting. Anonymous writes are FUTURE (out of scope).
  • The only writes a public viewer can do are triage-submit + upvote + comment — three NEW narrow grants checked explicitly, NOT a relaxation of 6.4 canEdit (a public viewer is not a member, so canEdit is false for every normal write). No create/move/assign/status affordance appears on the public surface.
  • Visible vs hidden. The public view shows the read-only board / work item list + the public ROADMAP (status-grouped). Internal-only fields — assignees, estimates, internal comments — are HIDDEN via a public PROJECTION that strips them at the read layer (not merely a hidden UI).
  • The proven portal set (adopted from the mirror). Beyond view + submit: upvoting (the demand signal the triage queue sorts by), automatic duplicate detection on submit (surface an existing matching request so the user upvotes it instead of creating a dupe — Canny’s core behaviour), comments on public requests, and a public roadmap with status tracking (submitted → planned → in progress → done).

This EXTENDS 6.11 (Triage) + 6.4 (access levels), never re-implements them. A public submission lands in the SAME triage queue (born a work_item in the triage state, excluded from every normal read), reusing 6.11.4’s intake path (adding cross-org-account submit + the dedupe pre-check) and 6.11.3’s queue (adding the vote-count sort); it adds no second submissions table and no second access policy.

Scope: the public-surface design (6.12.1); the public-access-level semantics decision (6.12.2); the schema + access-check extension + the publicOverviewMd project field + migration (6.12.3); the public read-only project view + the OVERVIEW/README landing (the default public tab), internal fields hidden, anonymous + server-rendered + SEO/GEO (head metadata, JSON-LD, semantic HTML, sitemap) (6.12.4); cross-account submit-to-triage + duplicate detection (6.12.5); upvoting + comments on public requests (6.12.6); the public roadmap view with status tracking (6.12.7); the "make public" toggle + the shareable public link + the Overview/README authoring editor in project settings (6.12.8); the access + dedupe + voting tests (6.12.9); the public submit-request form + duplicate-detection "upvote this instead" UI (6.12.11) and the public request detail page + comment composer/thread UI (6.12.12) — the public WRITE surfaces that wire the 6.12.5/6.12.6 backend into the public pages; and the cross-org e2e (6.12.10), which drives all of them.

The public OVERVIEW / README (Yue, design iteration 2026-06-13). The public landing leads with a modern, GitHub-README-style project intro — a hero (logo + name + tagline + at-a-glance stats + CTAs) + an authored Markdown body + a links/stats sidebar — and is the DEFAULT public tab (rung 1: GitHub puts the README on the repo home; Canny / Productboard / Plane / OpenProject public projects open on an about/overview, not the raw board). The content is a new nullable project.publicOverviewMd Markdown field (a public-safe field in the public projection), authored by the project admin via the shipped MarkdownEditor in settings and rendered read-only via MarkdownView; an empty field falls back to a slim auto-intro (never a blank page). Design: design/public-projects/ Panel 1 (6.12.1, the design gate — DONE).

Out of scope (named so they land in their own story, not here): ANONYMOUS / logged-out WRITES — submitting / upvoting / commenting without an account (a future story — needs an anonymous-identity model + heavier abuse controls; anonymous READ is now IN scope, only the writes need sign-in); a fully custom-branded / white-label public portal domain; AI-assisted dedupe suggestion beyond the deterministic match (an Epic-7 planner enhancement); public analytics / vote-trend dashboards; and email digests of public activity.

Verification

  • Pull the Story branch; run the migration + pnpm db:seed against the local Postgres (localhost:5433); pnpm dev.
  • Make-public + the share link. As the motir project admin, open project settings → set the project public → a shareable public link appears (and can be disabled/rotated). Confirm the access level now reads public and the four-level control shows public > open > limited > private.
  • Anonymous public read (the load-bearing check). While LOGGED OUT (no session), open the public link → the read-only board / work item list + the public roadmap render with NO sign-in; there are NO edit affordances (no create / move / assign / status controls); assignees, estimates, and internal comments are absent from every work item; and view-source shows a real <h1>, <meta>/OpenGraph tags, and a JSON-LD CollectionPage/SoftwareApplication block (SEO/GEO). Confirm hitting a NON-public project gets 404-not-403 (not forbidden), proving public is the only cross-org read exception. Confirm a write control shows a sign-in-to-act prompt.
  • Submit + duplicate detection (signed in). Sign in as a SECOND Motir account in a DIFFERENT org with NO membership in the public project’s org/workspace, then submit a feature request whose title matches an existing public request → the dedupe surfaces the existing matching request(s) and offers to upvote it instead of creating a dupe; upvoting it increments the count (and the item is NOT duplicated). Then submit a genuinely-new request → it lands in the project’s triage queue (6.11) with cross-org-account attribution, invisible to the normal tree until an admin promotes it.
  • Upvote + comment. As the second account, upvote a public request (a second upvote from the same account is a no-op / toggle, never a double count) and add a comment; confirm the comment shows on the public request and the vote raises the request’s position in the project admin’s triage queue (the demand signal).
  • The public roadmap. Confirm the roadmap groups public-facing items by status (submitted → planned → in progress → done) with vote counts, paginated (no load-all), and reflects an item’s status as the admin advances it.
  • pnpm test (6.12.9) covers: the access matrix (a cross-org account READS a public project but every normal write is blocked; the three permitted writes — submit / upvote / comment — succeed; a non-public project is 404 cross-org), the duplicate-detection match, and one-vote-per-account, all on a real Postgres respecting the per-file coverage gate.
  • 4-layer + token review. No raw Prisma in any route; the public read goes through the public projection in the service/repository layer; every write (submit/upvote/comment) routes through a service → workItemsService where it mutates a work_item; the public surfaces reference only --el-* / [data-display-style] tokens + shipped components/ui/*.
  • Dep audit. Confirm no 6.12 subtask references any id > 6.12 (deps are 6.12.x / 6.11.x / 6.4.x only).
  • If every step holds, approve and merge the Story PR. If anything fails, comment with what didn’t work and Motir will produce a follow-up Subtask under the same Story.
Child work items