Prioritized work items for CanopyCMS development. See AGENTS.md for project goals and working agreements.
Scope — which backlog is which (added 2026-08-13 after a full audit). This file is the unranked feature roadmap: capabilities we may want, with no severity rubric. Defects, debt and deferred fixes live in
.claude/future-tasks/with P0–P3 severities, per-item files, and a CI guard (pnpm lint:tasks). Roughly half the items below are completion records rather than open work; several others are already tracked as task files, noted inline. If you are looking for "what should I fix next", read.claude/future-tasks/index.md, not this file.Item numbering is load-bearing —
api/entries.ts:249cites "BACKLOG #19" in a source comment. Do not renumber.
- Bot-driven commit/push + PR creation; update branch metadata/registry status
- Reviewer flow: request changes/unlock, lock on submit, include PR URL/number
- Decide worker vs in-request for git/PR; keep abstraction so either works
- Targeting GitHub only to start
- PR submission uses Octokit with bot PAT; creates branch commits, pushes, and opens/updates PRs
- Submission locks editing until withdrawn (draft PR), reviewer requests changes, or after rejection/merge
- Consider @simulacrum/github-api-simulator for testing
- Comment threads stored as
.canopy-meta/comments.jsoninside branch clone (non-committed by default; pluggable storage if persistence beyond clone is needed) — path corrected 2026-08-13; the shipped location is.canopy-meta/, seecomment-store.ts:88 - Bot can mirror to PR comments if desired
- Post-merge: close/delete remote branch, mark branch clone read-only or archived; keep minimal metadata for history
- Show GitHub diff link to reviewers; basic status polling
- Withdraw or reviewer "request changes" re-opens (draft PR) to prevent reviewers seeing moving targets
- Full workflow test: create thread → add reply → resolve
- Test file:
packages/canopycms/src/editor/comments/CommentFlow.integration.test.tsx - Key test points: comment store updates, UI reflects state, thread sorting (unresolved first), resolve permissions, error states
- Setup: mock comment-store, mock user auth, test entry data, MantineProvider wrapper
- Single thread: no navigation arrows, no "1/1" counter, still show "+ New"
- Many threads (10+): boundary navigation, counter accuracy, scrolling performance, peekaboo preview
- Peekaboo rendering: 50px sliver of next thread, works at end of carousel
- Resolved filtering: unresolved first, then by createdAt
- Empty states: field with 0 threads shows "Comments • + New", entry/branch empty states
- Nested fields: deeply nested paths (
blocks[2].items[3].title), block reordering preserves comments, block deletion preserves comments (orphaned but not lost) - Files:
ThreadCarousel.test.tsx,FieldWrapper.test.tsx(may need to create)
- EntryComments: renders at top of form, uses ThreadCarousel, filters correctly (type === 'entry' && entryId matches), creates entry-level thread (no canopyPath)
- BranchComments: renders in BranchManager, uses ThreadCarousel, filters correctly (type === 'branch', no entryId)
- CommentsPanel: "Jump to entry" scrolls to form top, "Go to branch" opens BranchManager, threads grouped correctly
- Test files to create:
EntryComments.test.tsx,BranchComments.test.tsx
- ThreadCarousel loading states: "Create Thread" spinner, disable "+ New" while submitting, skeleton placeholder
- InlineCommentThread: already has
isSubmittingfor Reply/Resolve buttons - Optimistic updates: add comment to UI before API confirms, rollback on error
- Use Mantine
Skeleton,Loader, Buttonloadingprop
- Link PR comments to form fields
- Click link from PR comment → navigate to form field in editor
- Provide utilities for statically generated sites to create tables of contents / trees from schema ordering
— none of those four methods exist. Corrected 2026-08-13: the shippedAssetStoreinterface with methods:list,upload,delete,getSignedUrlAssetStore(assets/types.ts:47-77) isbeginUpload/writeStaging/putOriginal/putPublicObject/putMetaIfAbsent/getMeta/listMeta/deleteMeta. S3 and Local adapters both ship; LFS is the only unbuilt adapter (assets/factory.ts:39-42returnsundefined)- S3 adapter with presigned uploads for production
- LFS adapter surface
- Local adapter for dev/tests
- Enforce permissions and public URL building
- Media references stored in content as URLs; uploader handles permission checks and optional image transforms
- Uploads use pre-signed URLs in cloud modes; local dev avoids committing assets when prod uses S3
- Media manager UI: browsing, search, selection, deletion according to permissions
- Wire
imagePlugin({ imageUploadHandler })in MDXEditor to AssetStore so users can upload images directly from the editor toolbar
- Relational data: authors defined in their own collection, referencing from blog posts
- Navigator: search/add/delete entries
- Branch manager UX improvements
- Preview: guards/debounce, defaults from schema
- Loading guard to prevent flash
- Debounce draft updates
- Keep bracketed path mapping solid
- DRY up Mantine code with reusable components
- Validation/error display strategy
- Mermaid support
- Monaco code widget
- MDX support (mdx-editor)
- Filtering by collection/status in entry listing
- Keyboard shortcuts for common actions
- Figure out gray-matter usage (currently JSON heavy)
- Type-smoke test: render Editor with minimal entry, run
tsc --noEmitto catch API shape mismatches TODO: Decide if— settled; the source TODO is gone.normalizeContentPayloadshould merge top-levelbodywhen both nested{ format, data, body }and siblingbodypresenteditor-utils.ts:76-95implements it, with tests ateditor-utils.test.ts:136-153(verified 2026-08-13)- Field renderers for
numberanddatetimelanded 2026-08-13 (PR #220):NumberField,DateTimeField,NumberListField. They have no e2e coverage yet. (rich-textlanded in the same PR but was removed 2026-08-14 — it was only ever an alias formarkdown; see the Resolved section of.claude/future-tasks/index.md) - TODO: Refine preview base defaults from config (allow overrides, better root entry handling, clarify trailing-slash behavior)
- Background/scheduled sync from main; mark branches needing pull or in conflict
- Git strategy: rebase from main by default with conflict detection; merge fallback if needed
- UI to show conflicts
- Helper to abort/resolve is out of scope initially, but detection/reporting is in
- Infrastructure done: CmsWorker.syncGit() + rebaseActiveBranches() handles background sync and conflict detection.
- UI done (git-admin-observability epic, 2026-07): branch list shows sync-failed/pending-sync/conflict badges to all users; per-entry conflict notices were already in the editor.
- Done (git-admin-observability epic, 2026-07): admin System health panel (worker liveness + self-reported status file, task-queue inspection with retry/delete, branch-health scan with purge/repair, persistent per-branch rebase-failure records); worker logs ship to CloudWatch (PR #145).
- Remaining: structured logs for permission checks; feature flags and timeouts for long-running git tasks (task/simple-git timeouts exist; no flags).
- Custom form fields: host apps register components for field
typestrings (e.g.,codeEditorusing Monaco) - Package provides default widgets and example stories
- Host apps override per field type via registry
- DRY opportunities
- External library substitutions for bespoke code
- Security hardening: verify all API permissions, control over who does what
- Auth caching done: CachingAuthPlugin + FileBasedAuthCache for networkless Lambda operation
- User metadata caching done: Auth cache populated by worker daemon (Clerk or dev auth)
- Remaining: Evaluate if additional caching needed (content reads, computed data) based on performance
- 4 tests skipped due to jsdom async issues
- Research Mantine testing patterns (docs, GitHub issues, community solutions)
- Alternatives: test state changes directly (bypass button click), mock Mantine Button, use Playwright/Cypress for E2E
- Evaluate cost/benefit: 98.3% coverage is high, functionality works in production
- Files:
InlineCommentThread.test.tsx(2 skipped),ThreadCarousel.test.tsx(2 skipped)
canopycms migratehandles the generic rename +.collection.jsonscaffold; order defaults to alphabetical- Add opt-in support for common source conventions (e.g. Nextra
_meta.json→order+ labels) - Reference implementation: docs-site-proto's
scripts/migrate-content.ts
reportError(message, fieldPath)surfaces an alert over the preview pane (done)- Follow-up: when
fieldPathis present, also outline/highlight the matching form field (reuse the click-to-focus mechanics)
ensureRemoteonly runs on workspace creation; a workspace with a legacy/wrong origin (e.g. pre-prod-sim-rename artifact pointing at real GitHub, observed by docs-site-proto) keeps it forever- Consider re-running remote configuration on workspace open (cost: extra git calls per request) or a one-time startup sweep over
content-branches/*
- Content listing fans out file reads with unbounded
Promise.all: across collections (content-listing.tslistEntries,api/entries.tsnon-recursive + recursive paths) and across files within a collection (content-listing.tslistCollectionEntries). The editor's initial entry list (nocollectionparam) reads every entry file in the whole content tree concurrently. - This is intentional and consistent across all listing sites (verified in branch review of
fix/entries-list-perf-and-loading-state, 2026-06); it is the point of the per-request batch-auth optimization.MAX_ENTRIES_PER_PAGEcaps the response, not the FS fan-out. On local FS (dev) it is fine. - Risk is prod EFS only: a very large tree could hit throttling / EMFILE. Do not band-aid one call site — if it ever bites, add a single shared bounded-concurrency helper (e.g.
p-limit, ~16–32) applied uniformly to all three listing sites. The repo has no concurrency helper today, so this is a deliberate, holistic change.
-
Deployment Infrastructure ✅
- Lambda + EFS + EC2 Worker architecture (~$5-9/month, no NAT Gateway)
remote.gitauto-detection (aligns prod with dev bare repo pattern)- Auth caching:
CachingAuthPlugin+FileBasedAuthCachefor networkless operation - Clerk + dev auth both support token verifiers and cache writers
- Async task queue for GitHub operations (file-based on EFS)
CmsWorkerdaemon (auth-agnostic, cloud-agnostic, in canopycms core)- CDK constructs:
CanopyCmsService+CanopyCmsDistribution - Bootstrapping:
npx canopycms init+npx canopycms init-deploy aws - Worker CLI:
npx canopycms worker run-oncefor dev - See docs/deploying-to-aws.md
-
Other Framework Support ✅
- Next.js code abstracted into
canopycms-nextpackage - Core
canopycms/httpmodule provides framework-agnostic types and handler - Auth plugins use generic
CanopyRequestinterface - New adapters (Express, Hono, etc.) just need to convert request/response types
- Next.js code abstracted into
6 skipped tests repo-wide (count corrected 2026-08-13 — it said 4). The four
Mantine Button async-onClick-in-jsdom ones are ThreadCarousel.test.tsx:220,242
and InlineCommentThread.test.tsx:145,272; the other two are unrelated —
Editor.integration.test.tsx:1687 and editor-config.test.ts:152. The four
below involve Mantine Button async onClick in jsdom:
- "allows adding a reply"
- "calls onResolve when resolve button is clicked"
- "opens new thread box when New button clicked"
- "displays error when comment creation fails"
All functionality works in production.