diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 4a2a0951..0e6044e6 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -154,6 +154,7 @@ When posting issue comments, use concise markdown with: - Issues: `//issues/` (e.g., `/PAP/issues/PAP-224`) - Issue comments: `//issues/#comment-` (deep link to a specific comment) +- Issue documents: `//issues/#document-` (deep link to a specific document such as `plan`) - Agents: `//agents/` (e.g., `/PAP/agents/claudecoder`) - Projects: `//projects/` (id fallback allowed) - Approvals: `//approvals/` @@ -177,6 +178,13 @@ Submitted CTO hire request and linked it for board review. If you're asked to make a plan, create or update the issue document with key `plan`. Do not append plans into the issue description anymore. If you're asked for plan revisions, update that same `plan` document. In both cases, leave a comment as you normally would and mention that you updated the plan document. +When you mention a plan or another issue document in a comment, include a direct document link using the key: + +- Plan: `//issues/#document-plan` +- Generic document: `//issues/#document-` + +If the issue identifier is available, prefer the document deep link over a plain issue link so the reader lands directly on the updated document. + If you're asked to make a plan, _do not mark the issue as done_. Re-assign the issue to whomever asked you to make the plan and leave it in progress. Recommended API flow: diff --git a/ui/src/components/IssueDocumentsSection.tsx b/ui/src/components/IssueDocumentsSection.tsx index a2685911..17e48f38 100644 --- a/ui/src/components/IssueDocumentsSection.tsx +++ b/ui/src/components/IssueDocumentsSection.tsx @@ -1,10 +1,11 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { Issue } from "@paperclipai/shared"; +import { useLocation } from "@/lib/router"; import { issuesApi } from "../api/issues"; import { useAutosaveIndicator } from "../hooks/useAutosaveIndicator"; import { queryKeys } from "../lib/queryKeys"; -import { relativeTime } from "../lib/utils"; +import { cn, relativeTime } from "../lib/utils"; import { MarkdownBody } from "./MarkdownBody"; import { MarkdownEditor, type MentionOption } from "./MarkdownEditor"; import { Button } from "@/components/ui/button"; @@ -73,12 +74,15 @@ export function IssueDocumentsSection({ extraActions?: ReactNode; }) { const queryClient = useQueryClient(); + const location = useLocation(); const [confirmDeleteKey, setConfirmDeleteKey] = useState(null); const [error, setError] = useState(null); const [draft, setDraft] = useState(null); const [foldedDocumentKeys, setFoldedDocumentKeys] = useState(() => loadFoldedDocumentKeys(issue.id)); const [autosaveDocumentKey, setAutosaveDocumentKey] = useState(null); + const [highlightDocumentKey, setHighlightDocumentKey] = useState(null); const autosaveDebounceRef = useRef | null>(null); + const hasScrolledToHashRef = useRef(false); const { state: autosaveState, markDirty, @@ -287,6 +291,10 @@ export function IssueDocumentsSection({ setFoldedDocumentKeys(loadFoldedDocumentKeys(issue.id)); }, [issue.id]); + useEffect(() => { + hasScrolledToHashRef.current = false; + }, [issue.id, location.hash]); + useEffect(() => { const validKeys = new Set(sortedDocuments.map((doc) => doc.key)); setFoldedDocumentKeys((current) => { @@ -302,6 +310,23 @@ export function IssueDocumentsSection({ saveFoldedDocumentKeys(issue.id, foldedDocumentKeys); }, [foldedDocumentKeys, issue.id]); + useEffect(() => { + const hash = location.hash; + if (!hash.startsWith("#document-")) return; + const documentKey = decodeURIComponent(hash.slice("#document-".length)); + const targetExists = sortedDocuments.some((doc) => doc.key === documentKey) + || (documentKey === "plan" && Boolean(issue.legacyPlanDocument)); + if (!targetExists || hasScrolledToHashRef.current) return; + setFoldedDocumentKeys((current) => current.filter((key) => key !== documentKey)); + const element = document.getElementById(`document-${documentKey}`); + if (!element) return; + hasScrolledToHashRef.current = true; + setHighlightDocumentKey(documentKey); + element.scrollIntoView({ behavior: "smooth", block: "center" }); + const timer = setTimeout(() => setHighlightDocumentKey((current) => current === documentKey ? null : current), 3000); + return () => clearTimeout(timer); + }, [issue.legacyPlanDocument, location.hash, sortedDocuments]); + useEffect(() => { return () => { if (autosaveDebounceRef.current) { @@ -430,7 +455,13 @@ export function IssueDocumentsSection({ )} {!hasRealPlan && issue.legacyPlanDocument ? ( -
+
@@ -450,7 +481,14 @@ export function IssueDocumentsSection({ const showTitle = !isPlanKey(doc.key) && !!doc.title?.trim() && !titlesMatchKey(doc.title, doc.key); return ( -
+
@@ -466,9 +504,12 @@ export function IssueDocumentsSection({ {doc.key} - + rev {doc.latestRevisionNumber} • updated {relativeTime(doc.updatedAt)} - +
{showTitle &&

{doc.title}

}