Files
paperclip/docs/plans/2026-03-13-issue-documents-plan.md
2026-03-13 21:30:48 -05:00

14 KiB

Issue Documents Plan

Status: Draft
Owner: Backend + UI + Agent Protocol
Date: 2026-03-13
Primary issue: PAP-448

Summary

Add first-class documents to Paperclip as editable, revisioned, company-scoped text artifacts that can be linked to issues.

The first required convention is a document with key plan.

This solves the immediate workflow problem in PAP-448:

  • plans should stop living inside issue descriptions as <plan> blocks
  • agents and board users should be able to create/update issue documents directly
  • GET /api/issues/:id should include the full plan document and expose the other available documents
  • issue detail should render documents under the description

This should be built as the text-document slice of the broader artifact system, not as a replacement for attachments/assets.

Documents vs attachments vs artifacts

  • Documents: editable text content with stable keys and revision history.
  • Attachments: uploaded/generated opaque files backed by storage (assets + issue_attachments).
  • Artifacts: later umbrella/read-model that can unify documents, attachments, previews, and workspace files.

Recommendation:

  • implement issue documents now
  • keep existing attachments as-is
  • defer full artifact unification until there is a second real consumer beyond issue documents + attachments

This keeps PAP-448 focused while still fitting the larger artifact direction.

Goals

  1. Give issues first-class keyed documents, starting with plan.
  2. Make documents editable by board users and same-company agents with issue access.
  3. Preserve change history with append-only revisions.
  4. Make the plan document automatically available in the normal issue fetch used by agents/heartbeats.
  5. Replace the current <plan>-in-description convention in skills/docs.
  6. Keep the design compatible with a future artifact/deliverables layer.

Non-Goals

  • full collaborative doc editing
  • binary-file version history
  • browser IDE or workspace editor
  • full artifact-system implementation in the same change
  • generalized polymorphic relations for every entity type on day one

Product Decisions

1. Keyed issue documents

Each issue can have multiple documents. Each document relation has a stable key:

  • plan
  • design
  • notes
  • report
  • custom keys later

Key rules:

  • unique per issue, case-insensitive
  • normalized to lowercase slug form
  • machine-oriented and stable
  • title is separate and user-facing

The plan key is conventional and reserved by Paperclip workflow/docs.

2. Text-first v1

V1 documents should be text-first, not arbitrary blobs.

Recommended supported formats:

  • markdown
  • plain_text
  • json
  • html

Recommendation:

  • optimize UI for markdown
  • allow raw editing for the others
  • keep PDFs/images/CSVs/etc as attachments/artifacts, not editable documents

3. Revision model

Every document update creates a new immutable revision.

The current document row stores the latest snapshot for fast reads.

4. Concurrency model

Do not use silent last-write-wins.

Updates should include baseRevisionId:

  • create: no base revision required
  • update: baseRevisionId must match current latest revision
  • mismatch: return 409 Conflict

This is important because both board users and agents may edit the same document.

5. Issue fetch behavior

GET /api/issues/:id should include:

  • full planDocument when a plan document exists
  • documentSummaries for all linked documents

It should not inline every document body by default.

This keeps issue fetches useful for agents without making every issue payload unbounded.

6. Legacy <plan> compatibility

If an issue has no plan document but its description contains a legacy <plan> block:

  • expose that as a legacy read-only fallback in API/UI
  • mark it as legacy/synthetic
  • prefer a real plan document when both exist

Recommendation:

  • do not auto-rewrite old issue descriptions in the first rollout
  • provide an explicit import/migrate path later

Proposed Data Model

Recommendation: make documents first-class, but keep issue linkage explicit via a join table.

This preserves foreign keys today and gives a clean path to future project_documents or company_documents tables later.

Tables

documents

Canonical text document record.

Suggested columns:

  • id
  • company_id
  • title
  • format
  • latest_body
  • latest_revision_id
  • latest_revision_number
  • created_by_agent_id
  • created_by_user_id
  • updated_by_agent_id
  • updated_by_user_id
  • created_at
  • updated_at

document_revisions

Append-only history.

Suggested columns:

  • id
  • company_id
  • document_id
  • revision_number
  • body
  • change_summary
  • created_by_agent_id
  • created_by_user_id
  • created_at

Constraints:

  • unique (document_id, revision_number)

issue_documents

Issue relation + workflow key.

Suggested columns:

  • id
  • company_id
  • issue_id
  • document_id
  • key
  • created_at
  • updated_at

Constraints:

  • unique (company_id, issue_id, key)
  • unique (document_id) to keep one issue relation per document in v1

Why not use assets for this?

Because assets solves blob storage, not:

  • stable keyed semantics like plan
  • inline text editing
  • revision history
  • optimistic concurrency
  • cheap inclusion in GET /issues/:id

Documents and attachments should remain separate primitives, then meet later in a deliverables/artifact read-model.

Shared Types and API Contract

New shared types

Add:

  • DocumentFormat
  • IssueDocument
  • IssueDocumentSummary
  • DocumentRevision

Recommended IssueDocument shape:

type DocumentFormat = "markdown" | "plain_text" | "json" | "html";

interface IssueDocument {
  id: string;
  companyId: string;
  issueId: string;
  key: string;
  title: string | null;
  format: DocumentFormat;
  body: string;
  latestRevisionId: string;
  latestRevisionNumber: number;
  createdByAgentId: string | null;
  createdByUserId: string | null;
  updatedByAgentId: string | null;
  updatedByUserId: string | null;
  createdAt: Date;
  updatedAt: Date;
}

Recommended IssueDocumentSummary shape:

interface IssueDocumentSummary {
  id: string;
  key: string;
  title: string | null;
  format: DocumentFormat;
  latestRevisionId: string;
  latestRevisionNumber: number;
  updatedAt: Date;
}

Issue type enrichment

Extend Issue with:

interface Issue {
  ...
  planDocument?: IssueDocument | null;
  documentSummaries?: IssueDocumentSummary[];
  legacyPlanDocument?: {
    key: "plan";
    body: string;
    source: "issue_description";
  } | null;
}

This directly satisfies the PAP-448 requirement for heartbeat/API issue fetches.

API endpoints

Recommended endpoints:

  • GET /api/issues/:issueId/documents
  • GET /api/issues/:issueId/documents/:key
  • PUT /api/issues/:issueId/documents/:key
  • GET /api/issues/:issueId/documents/:key/revisions
  • DELETE /api/issues/:issueId/documents/:key optionally board-only in v1

Recommended PUT body:

{
  title?: string | null;
  format: "markdown" | "plain_text" | "json" | "html";
  body: string;
  changeSummary?: string | null;
  baseRevisionId?: string | null;
}

Behavior:

  • missing document + no baseRevisionId: create
  • existing document + matching baseRevisionId: update
  • existing document + stale baseRevisionId: 409

Authorization and invariants

  • all document records are company-scoped
  • issue relation must belong to same company
  • board access follows existing issue access rules
  • agent access follows existing same-company issue access rules
  • every mutation writes activity log entries

Recommended delete rule for v1:

  • board can delete documents
  • agents can create/update, but not delete

That keeps automated systems from removing canonical docs too easily.

UI Plan

Issue detail

Add a new Documents section directly under the issue description.

Recommended behavior:

  • show plan first when present
  • show other documents below it
  • render a gist-like header:
    • key
    • title
    • last updated metadata
    • revision number
  • support inline edit
  • support create new document by key
  • support revision history drawer or sheet

Recommended presentation order:

  1. Description
  2. Documents
  3. Attachments
  4. Comments / activity / sub-issues

This matches the request that documents live under the description while still leaving attachments available.

Editing UX

Recommendation:

  • use markdown preview + raw edit toggle for markdown docs
  • use raw textarea editor for non-markdown docs in v1
  • show explicit save conflicts on 409
  • show a clear empty state: "No documents yet"

Legacy plan rendering

If there is no stored plan document but legacy <plan> exists:

  • show it in the Documents section
  • mark it Legacy plan from description
  • offer create/import in a later pass

Agent Protocol and Skills

Update the Paperclip agent workflow so planning no longer edits the issue description.

Required changes:

  • update skills/paperclip/SKILL.md
  • replace the <plan> instructions with document creation/update instructions
  • document the new endpoints in docs/api/issues.md
  • update any internal planning docs that still teach inline <plan> blocks

New rule:

  • when asked to make a plan for an issue, create or update the issue document with key plan
  • leave a comment that the plan document was created/updated
  • do not mark the issue done

Relationship to the Artifact Plan

This work should explicitly feed the broader artifact/deliverables direction.

Recommendation:

  • keep documents as their own primitive in this change
  • add document to any future ArtifactKind
  • later build a deliverables read-model that aggregates:
    • issue documents
    • issue attachments
    • preview URLs
    • workspace-file references

The artifact proposal currently has no explicit document kind. It should.

Recommended future shape:

type ArtifactKind =
  | "document"
  | "attachment"
  | "workspace_file"
  | "preview"
  | "report_link";

Implementation Phases

Phase 1: Shared contract and schema

Files:

  • packages/db/src/schema/documents.ts
  • packages/db/src/schema/document_revisions.ts
  • packages/db/src/schema/issue_documents.ts
  • packages/db/src/schema/index.ts
  • packages/db/src/migrations/*
  • packages/shared/src/types/issue.ts
  • packages/shared/src/validators/issue.ts or new document validator file
  • packages/shared/src/index.ts

Acceptance:

  • schema enforces one key per issue
  • revisions are append-only
  • shared types expose plan/document fields on issue fetch

Phase 2: Server services and routes

Files:

  • server/src/services/issues.ts or server/src/services/documents.ts
  • server/src/routes/issues.ts
  • server/src/services/activity.ts callsites

Behavior:

  • list/get/upsert/delete documents
  • revision listing
  • GET /issues/:id returns planDocument + documentSummaries
  • company boundary checks match issue routes

Acceptance:

  • agents and board can fetch/update same-company issue documents
  • stale edits return 409
  • activity timeline shows document changes

Phase 3: UI issue documents surface

Files:

  • ui/src/api/issues.ts
  • ui/src/lib/queryKeys.ts
  • ui/src/pages/IssueDetail.tsx
  • new reusable document UI component if needed

Behavior:

  • render plan + documents under description
  • create/update by key
  • open revision history
  • show conflicts/errors clearly

Acceptance:

  • board can create a plan doc from issue detail
  • updated plan appears immediately
  • issue detail no longer depends on description-embedded <plan>

Phase 4: Skills/docs migration

Files:

  • skills/paperclip/SKILL.md
  • docs/api/issues.md
  • doc/SPEC-implementation.md
  • relevant plan/docs that mention <plan>

Acceptance:

  • planning guidance references issue documents, not inline issue description tags
  • API docs describe the new document endpoints and issue payload additions

Phase 5: Legacy compatibility and follow-up

Behavior:

  • read legacy <plan> blocks as fallback
  • optionally add explicit import/migration command later

Follow-up, not required for first merge:

  • deliverables/artifact read-model
  • project/company documents
  • comment-linked documents
  • diff view between revisions

Test Plan

Server

  • document create/read/update/delete lifecycle
  • revision numbering
  • baseRevisionId conflict handling
  • company boundary enforcement
  • agent vs board authorization
  • issue fetch includes planDocument and document summaries
  • legacy <plan> fallback behavior
  • activity log mutation coverage

UI

  • issue detail shows plan document
  • create/update flows invalidate queries correctly
  • conflict and validation errors are surfaced
  • legacy plan fallback renders correctly

Verification

Run before implementation is declared complete:

pnpm -r typecheck
pnpm test:run
pnpm build

Open Questions

  1. Should v1 documents be markdown-only, with json/html/plain_text deferred? Recommendation: allow all four in API, optimize UI for markdown only.

  2. Should agents be allowed to create arbitrary keys, or only conventional keys? Recommendation: allow arbitrary keys with normalized validation; reserve plan as special behavior only.

  3. Should delete exist in v1? Recommendation: yes, but board-only.

  4. Should legacy <plan> blocks ever be auto-migrated? Recommendation: no automatic mutation in the first rollout.

  5. Should documents appear inside a future Deliverables section or remain a top-level Issue section? Recommendation: keep a dedicated Documents section now; later also expose them in Deliverables if an aggregated artifact view is added.

Final Recommendation

Ship issue documents as a focused, text-first primitive now.

Do not try to solve full artifact unification in the same implementation.

Use:

  • first-class document tables
  • issue-level keyed linkage
  • append-only revisions
  • planDocument embedded in normal issue fetches
  • legacy <plan> fallback
  • skill/docs migration away from description-embedded plans

This addresses the real planning workflow problem immediately and leaves the artifact system room to grow cleanly afterward.