feat(issues): add issue documents and inline editing

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-03-13 21:30:48 -05:00
parent 0f3e9937f6
commit 45998aa9a0
32 changed files with 9157 additions and 97 deletions

View File

@@ -1,9 +1,9 @@
---
title: Issues
summary: Issue CRUD, checkout/release, comments, and attachments
summary: Issue CRUD, checkout/release, comments, documents, and attachments
---
Issues are the unit of work in Paperclip. They support hierarchical relationships, atomic checkout, comments, and file attachments.
Issues are the unit of work in Paperclip. They support hierarchical relationships, atomic checkout, comments, keyed text documents, and file attachments.
## List Issues
@@ -29,6 +29,12 @@ GET /api/issues/{issueId}
Returns the issue with `project`, `goal`, and `ancestors` (parent chain with their projects and goals).
The response also includes:
- `planDocument`: the full text of the issue document with key `plan`, when present
- `documentSummaries`: metadata for all linked issue documents
- `legacyPlanDocument`: a read-only fallback when the description still contains an old `<plan>` block
## Create Issue
```
@@ -100,6 +106,54 @@ POST /api/issues/{issueId}/comments
@-mentions (`@AgentName`) in comments trigger heartbeats for the mentioned agent.
## Documents
Documents are editable, revisioned, text-first issue artifacts keyed by a stable identifier such as `plan`, `design`, or `notes`.
### List
```
GET /api/issues/{issueId}/documents
```
### Get By Key
```
GET /api/issues/{issueId}/documents/{key}
```
### Create Or Update
```
PUT /api/issues/{issueId}/documents/{key}
{
"title": "Implementation plan",
"format": "markdown",
"body": "# Plan\n\n...",
"baseRevisionId": "{latestRevisionId}"
}
```
Rules:
- omit `baseRevisionId` when creating a new document
- provide the current `baseRevisionId` when updating an existing document
- stale `baseRevisionId` returns `409 Conflict`
### Revision History
```
GET /api/issues/{issueId}/documents/{key}/revisions
```
### Delete
```
DELETE /api/issues/{issueId}/documents/{key}
```
Delete is board-only in the current implementation.
## Attachments
### Upload

View File

@@ -0,0 +1,569 @@
# 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.
## Recommended Product Shape
### 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:
```ts
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:
```ts
interface IssueDocumentSummary {
id: string;
key: string;
title: string | null;
format: DocumentFormat;
latestRevisionId: string;
latestRevisionNumber: number;
updatedAt: Date;
}
```
## Issue type enrichment
Extend `Issue` with:
```ts
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:
```ts
{
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:
```ts
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:
```sh
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.