Scribegate¶
A simplified, self-hosted markdown collaboration platform with editorial review workflows.
scribegate.dev — "your writing passes through the gate"
1. Vision¶
Scribegate is what you get when a wiki and a Git forge have a baby: contributors propose changes to markdown documents through a simple web UI, reviewers approve or reject them, and the approved version becomes the published truth. No CLI, no branches, no merge conflicts — just writing, reviewing, and publishing.
The Gap It Fills¶
| Category | Examples | What's Missing |
|---|---|---|
| Real-time collaborative editors | HedgeDoc, HackMD | No approval workflow; everyone edits the "live" version directly |
| Full Git forges | Gitea, GitLab CE, Forgejo | Expose full Git complexity (branches, CI/CD, CLI) to content authors |
| Wikis with version history | Wiki.js, Otter Wiki | Version tracking but no propose → review → approve cycle |
Scribegate occupies the space between these: the editorial rigor of pull requests, without the developer tooling overhead.
Design Principles¶
- Non-technical authors first. A contributor should never see a terminal, a branch name, or a merge conflict.
- The repository is the truth. There is one canonical version of every document. Proposals are ephemeral until approved.
- Minimal surface area. Ship the smallest useful thing. No CI/CD, no issue tracker, no wikis-about-wikis.
- Self-hosted by default. A single Docker container or dotnet publish, a volume for data, done.
2. Domain Model¶
Working backwards from the end goal — a reader views an approved, published markdown page — these are the core entities.
2.1 Repository¶
The top-level container. An Scribegate instance can host multiple repositories (e.g., "Company Handbook", "API Docs", "Meeting Notes").
| Property | Description |
|---|---|
Id |
Unique identifier |
Name |
Display name |
Slug |
URL-safe identifier (e.g., company-handbook) |
Description |
Optional summary |
DefaultBranch |
Always main — not user-facing, but internally the concept of "the truth" |
Visibility |
Public (anyone can read) or Private (authenticated users only) |
CreatedAt |
Timestamp |
2.2 Document¶
A markdown file within a repository. Documents are organized in a flat-or-nested folder structure, like files on disk.
| Property | Description |
|---|---|
Id |
Unique identifier |
RepositoryId |
Parent repository |
Path |
Full path including filename (e.g., onboarding/first-week.md) |
CurrentRevisionId |
Points to the latest approved revision |
CreatedAt |
Timestamp |
CreatedBy |
User who created the document |
2.3 Revision¶
An immutable snapshot of a document's content at a point in time. Every approved change creates a new revision. This is the "commit" analogy, but simpler — it's always one document, one change.
| Property | Description |
|---|---|
Id |
Unique identifier |
DocumentId |
Parent document |
Content |
The full markdown content at this point |
Message |
Short description of what changed (like a commit message) |
CreatedAt |
Timestamp |
CreatedBy |
The user whose proposal was approved (or who made the initial commit) |
ParentRevisionId |
The revision this was based on (nullable for the first revision) |
2.4 Proposal¶
The central workflow entity — analogous to a Pull Request, but scoped to a single document. A contributor writes or edits markdown and submits it for review.
| Property | Description |
|---|---|
Id |
Unique identifier |
DocumentId |
The document being changed (null if proposing a new document) |
RepositoryId |
Parent repository |
Title |
Short summary (e.g., "Update vacation policy for 2026") |
Description |
Optional longer explanation of the change |
ProposedContent |
The full markdown content being proposed |
BaseRevisionId |
The revision the author started from (for diffing) |
Status |
Draft → Open → Approved / Rejected / Withdrawn |
CreatedAt |
Timestamp |
CreatedBy |
The contributor |
ResolvedAt |
When it was approved/rejected/withdrawn |
ResolvedBy |
Who resolved it |
State machine:
2.5 Review¶
A reviewer's verdict on a proposal. Multiple reviewers can weigh in, but only one approval is needed to merge (configurable per repository).
| Property | Description |
|---|---|
Id |
Unique identifier |
ProposalId |
Parent proposal |
Verdict |
Approved / ChangesRequested / Comment |
Body |
Markdown-formatted review comment |
CreatedAt |
Timestamp |
CreatedBy |
The reviewer |
2.6 Comment¶
Thread-based discussion on a proposal, optionally anchored to a specific line in the diff.
| Property | Description |
|---|---|
Id |
Unique identifier |
ProposalId |
Parent proposal |
ParentCommentId |
For threaded replies (nullable) |
Body |
Markdown content |
LineReference |
Optional anchor to a line number in the proposed content |
CreatedAt |
Timestamp |
CreatedBy |
Author |
2.7 User¶
| Property | Description |
|---|---|
Id |
Unique identifier |
Username |
Display name |
Email |
For notifications |
PasswordHash |
Salted hash (or external auth reference) |
CreatedAt |
Timestamp |
2.8 RepositoryMembership¶
Maps users to repositories with a role.
| Property | Description |
|---|---|
UserId |
|
RepositoryId |
|
Role |
Reader / Contributor / Reviewer / Admin |
Role permissions:
| Action | Reader | Contributor | Reviewer | Admin |
|---|---|---|---|---|
| View published documents | ✓ | ✓ | ✓ | ✓ |
| Create proposals | ✓ | ✓ | ✓ | |
| Review & approve proposals | ✓ | ✓ | ||
| Manage repository settings | ✓ | |||
| Manage members | ✓ | |||
| Direct publish (skip proposal) | ✓ |
3. User Flows¶
3.1 Reading (the happy path)¶
- User navigates to a repository
- Sees a file tree of all documents
- Clicks a document → sees the rendered markdown
- Can browse revision history ("View history" → list of revisions with messages and timestamps)
3.2 Proposing a Change¶
- Contributor opens a published document and clicks "Propose Edit"
- A markdown editor opens, pre-filled with the current content
- Contributor edits the content (live preview alongside the editor)
- Contributor writes a title and optional description
- Clicks "Submit Proposal" → status becomes
Open - Reviewers are notified
Proposing a new document follows the same flow but starts from an empty editor via "New Document" in the file tree.
3.3 Reviewing a Proposal¶
- Reviewer opens the proposal
- Sees a side-by-side diff (current published version vs. proposed version)
- Can leave line-level comments or general comments
- Submits a review: Approve, Request Changes, or Comment-only
- If approved (and approval threshold met), the proposal is merged:
- A new Revision is created from the proposed content
- The Document's
CurrentRevisionIdis updated - The Proposal status becomes
Approved
3.4 Handling Staleness¶
If the document has been updated (by another approved proposal) since the contributor started editing, the proposal is marked as stale. The contributor is shown a warning and can:
- Rebase: open the editor again with the latest version, re-apply their changes manually
- Force submit: submit anyway (reviewer will see the full diff against the current version)
This is deliberately simple. No automatic merging, no conflict resolution algorithms. For markdown content authored by humans, a manual re-read is safer and faster than a three-way merge.
4. Key Screens¶
4.1 Repository Home¶
- Repository name and description
- File tree (collapsible folders)
- "New Document" button (for contributors+)
- Badge showing count of open proposals
4.2 Document View¶
- Rendered markdown (full-width, clean reading experience)
- Metadata bar: last updated, last author, revision count
- Actions: "Propose Edit" / "View History" / "Raw Markdown"
4.3 Document History¶
- Chronological list of revisions
- Each entry: message, author, timestamp, link to view that revision
- Diff between any two revisions (selectable)
4.4 Proposal View¶
- Title, description, author, status badge
- Tabbed interface:
- Changes: side-by-side or unified diff
- Discussion: threaded comments (general + line-anchored)
- Reviews: list of submitted reviews with verdicts
- Action buttons: "Approve" / "Request Changes" / "Comment" (for reviewers), "Withdraw" (for author)
4.5 Proposal List¶
- Filterable by status (Open / Approved / Rejected / All)
- Sortable by date, author
- Shows title, author, document path, status, review count
4.6 Editor¶
- Split pane: markdown source on the left, rendered preview on the right
- Toolbar with common formatting shortcuts (headings, bold, italic, links, images, code blocks)
- Title and description fields below the editor
- "Save Draft" and "Submit Proposal" buttons
- If editing an existing proposal in Draft status, "Update & Submit"
5. Technical Architecture¶
5.1 Stack¶
| Layer | Technology | Rationale |
|---|---|---|
| Backend framework | ASP.NET Core (via Vidyano) | Existing expertise; robust, performant, self-hostable |
| Database (primary) | SQLite via EF Core | Zero-config, file-based, free everywhere, trivial to host and backup |
| Database (future) | RavenDB adapter | For teams already running RavenDB; same storage interfaces, different implementation |
| Frontend | TypeScript + Lit components + SASS | Existing expertise; lightweight, no heavy framework overhead |
| Markdown rendering | Server-side via Markdig (.NET) | Fast, extensible, CommonMark-compliant |
| Diff engine | DiffPlex (.NET) | Mature .NET diff library, supports side-by-side and inline diffs |
| Auth | ASP.NET Core Identity or Vidyano's built-in auth | Supports local accounts; extensible to OIDC/LDAP later |
5.2 Storage Design¶
The storage layer is abstracted behind a repository/service interface, allowing multiple backends.
Primary: SQLite via EF Core
Tables map directly to domain entities:
Repositories → Id, Name, Slug, Description, Visibility, CreatedAt
Documents → Id, RepositoryId, Path, CurrentRevisionId, CreatedAt, CreatedBy
Revisions → Id, DocumentId, Content (TEXT), Message, CreatedAt, CreatedBy, ParentRevisionId
Proposals → Id, DocumentId, RepositoryId, Title, Description, ProposedContent (TEXT), BaseRevisionId, Status, CreatedAt, CreatedBy, ResolvedAt, ResolvedBy
Reviews → Id, ProposalId, Verdict, Body, CreatedAt, CreatedBy
Comments → Id, ProposalId, ParentCommentId, Body, LineReference, CreatedAt, CreatedBy
Users → Id, Username, Email, PasswordHash, CreatedAt
RepositoryMemberships → UserId, RepositoryId, Role
Full-text search via SQLite FTS5 on Document content and Proposal content.
Future: RavenDB adapter
A RavenDB storage adapter is planned for self-hosted users who prefer it. It would implement the same store interfaces (IRepositoryStore, etc.) using IDocumentSession, leveraging RavenDB's built-in full-text search and document model. No changes to the Core or Web layers would be needed.
Content storage consideration: Revision content and Proposal content are stored as full markdown strings (not diffs). This trades some storage space for radical simplicity — any revision can be rendered independently without replaying a chain. For typical documentation (tens of KB per document, hundreds of revisions), this is negligible.
5.3 Deployment¶
Target: single-container deployment.
The container bundles:
- The ASP.NET Core application
- The frontend SPA (TypeScript + Lit, built with Vite)
- A SQLite database file in /data
Configuration via environment variables or appsettings.json:
- Scribegate__BaseUrl — public URL for links in notifications
- Scribegate__DataPath — where SQLite stores data (default: /data)
- Scribegate__Jwt__ExpirationHours — JWT token lifetime (default: 24)
- ASPNETCORE_URLS — listen address (default: http://+:8080)
6. Hosting & Architecture¶
Scribegate supports both self-hosted and managed (scribegate.dev) deployment from day one.
6.1 Database: SQLite-First¶
SQLite via EF Core is the primary storage engine — zero-config, file-based, runs anywhere .NET runs. The storage layer is abstracted behind interfaces (IRepositoryStore, IDocumentStore, etc.) so a RavenDB adapter can be added later without changing any other code.
6.2 Self-Hosting Options¶
See self-hosting.md for detailed deployment guides covering Docker, Azure, fly.io, and bare metal.
6.3 Managed Hosting (scribegate.dev)¶
A managed tier at scribegate.dev provides hosted workspaces for teams that don't want to self-host. Multi-tenant architecture with per-tenant SQLite databases. Free and paid tiers available — see scribegate.dev for current plans.
6.4 Architecture Requirements¶
To support both self-hosted and managed hosting, the codebase needs:
-
Tenant isolation layer. In self-hosted mode, there's one implicit tenant (the whole instance). In managed mode, requests are routed by subdomain or path prefix to the correct tenant context.
-
Storage abstraction. A service interface that wraps EF Core + SQLite by default, with a RavenDB implementation available for teams that prefer it.
-
Limit enforcement. Configurable limits (max documents, max users, max storage) that are enforced in managed mode and set to "unlimited" (or very high) in self-hosted mode.
-
No external dependencies in the critical path. The app must work fully offline / air-gapped for self-hosted users. Email notifications, OIDC, etc. are all optional.
7. Scope & Milestones¶
Milestone 1 — "Read & Write" (MVP) ✓¶
Core reading and editing loop without review workflow.
- Repository CRUD
- Document CRUD (create, edit, view rendered markdown)
- Revision history (automatic on every save)
- File tree navigation
- Markdown editor with live preview
- Basic authentication (local accounts)
- Single-container deployment
Milestone 2 — "Propose & Review" ✓¶
The differentiating feature: editorial workflow.
- Proposal creation (from existing document or new)
- Proposal states (Draft → Open → Approved/Rejected/Withdrawn)
- Side-by-side diff view
- Review submission (Approve / Request Changes / Comment)
- Automatic revision creation on approval
- Staleness detection and rebase flow
- Role-based access (Reader / Contributor / Reviewer / Admin)
At this point, Scribegate delivers its core value proposition.
Milestone 3 — "Polish & Integrate"¶
- Line-level comments on diffs
- Email notifications (new proposal, review submitted, proposal approved)
- Full-text search across documents (SQLite FTS5)
- SSO/OIDC authentication (configurable via admin settings, available to all tiers)
- Configurable approval rules (per-repository, 1-10 required approvals)
- Document rename/move with history preservation
- Media/image uploads (local disk storage, MIME validation, storage quotas)
- Configurable tier/quota system (free/paid tiers, enforced or unlimited)
- Notification system with user preferences
- Expanded slug denylist (~100+ reserved words for future-proofing)
- Export repository as a zip of markdown files
Milestone 4 — "Ecosystem" (Future)¶
- Webhooks (on proposal created, approved, etc.)
- API for external integrations
- Git-compatible read-only access (clone the repo)
- Static site generation from repository content
- Markdown templates per repository
8. What Scribegate Is Not¶
Clarity on boundaries prevents scope creep:
- Not a real-time collaborative editor. One author per proposal. No simultaneous cursors.
- Not a Git server. No branches, no CLI push/pull, no merge strategies. (Read-only Git access is a future possibility, not a core feature.)
- Not a CMS. No themes, no page layouts, no publishing pipeline. It stores and serves markdown.
- Not an issue tracker. Proposals have discussion threads, but there's no separate "issues" concept.
- Not a wiki in the "anyone can edit live" sense. All changes go through proposals (except Admin direct publish).
9. Open Questions¶
-
Multi-document proposals. Should a single proposal be able to change multiple documents atomically? (Git PRs can, but it adds complexity. Recommendation: not in v1, revisit based on user feedback.)
-
~~Approval threshold default.~~ Resolved:
RequiredApprovalsis configurable per repository (1-10). Defaults to 1. Distinct approvals are counted per reviewer. -
RavenDB adapter scope. When should the RavenDB adapter be built? (Recommendation: after the SQLite-based product is stable and there's user demand. The storage interface abstraction is already in place, so adding it is straightforward.)
-
Markdown extensions. Which extensions beyond CommonMark? (Recommendation: GFM tables, task lists, syntax highlighting, and Mermaid diagrams. Align with what Markdig supports out of the box.)
-
Delete semantics. Can documents be deleted, or only archived? (Recommendation: soft-delete/archive. The revision history should never lose data.)
Appendix: The Git Analogy¶
For contributors familiar with Git, here's how Scribegate concepts map:
| Git Concept | Scribegate Concept | Key Difference |
|---|---|---|
| Repository | Repository | Same idea, scoped to markdown |
| Branch | (none) | There's only "the truth" (main) |
| Commit | Revision | Always one document, auto-created on approval |
| Working tree | Proposal (Draft) | The author's work-in-progress |
| Pull Request | Proposal (Open) | Scoped to a single document |
| Code Review | Review | Same idea, simpler verdicts |
| Merge | Approval | No merge strategies — the proposed content becomes the new revision |
| Conflict | Staleness | Manual resolution only, by design |