# Story 1.2-c: Complete Posts Collection (Story 1.2 split) **Status:** Done **Epic:** Epic 1 - Webflow to Payload CMS + Astro Migration **Priority:** P1 (High - Required for Story 1.9 Blog System) **Estimated Time:** 0.5 hour (30 minutes) ## Story **As a** CMS Administrator, **I want** a complete Posts collection with all necessary fields including excerpt, social images, and status tracking, **So that** blog posts have complete metadata for display and Story 1.9 (Blog System) can proceed. ## Context This is a Sprint 1 split story from the original Story 1.2 (Payload CMS Collections Definition). The Posts collection currently exists but is incomplete - missing 4 critical fields needed for the Blog System (Story 1.9). **Story Source:** - Split from `docs/prd/05-epic-stories.md` - Story 1.2 - Technical spec: `docs/prd/payload-cms-modification-plan.md` - Task 1.2.3 - Execution plan: `docs/prd/epic-1-execution-plan.md` - Story 1.2 Phase 3 **Current State:** - File exists at `apps/backend/src/collections/Posts/index.ts` - Has many fields including: title, heroImage, content, categories, publishedAt, authors, etc. - Has SEO plugin with meta.image (but this is for SEO, not social sharing) - Missing: excerpt, ogImage (social sharing), showInFooter, status ## Acceptance Criteria 1. **AC1 - excerpt Field Added**: Textarea field for article summary (200 char limit, multiline) 2. **AC2 - ogImage Field Added**: Upload field for social sharing image (separate from SEO meta.image) 3. **AC3 - showInFooter Field Added**: Checkbox field (default: false, sidebar position) 4. **AC4 - status Field Added**: Select field with options: draft, review, published (default: draft, sidebar) 5. **AC5 - Fields in Correct Tabs**: excerpt and ogImage in Content tab, showInFooter and status in sidebar 6. **AC6 - TypeScript Types Generated**: Running `pnpm build` regenerates payload-types.ts without errors 7. **AC7 - Admin UI Working**: All fields visible and functional in Payload Admin panel ## Previous Story Learnings (from Story 1.2) From Story 1.2 execution (43% complete): - Posts collection file already exists with complex tab structure - Has Content tab with heroImage and content - Has Meta tab with categories and relatedPosts (both sidebar position) - Has SEO tab (from plugin) with meta.image - Has publishedAt and authors in sidebar - **This is a modification task, not creation** ## Dev Technical Guidance ### Current Posts Collection Structure (Key Parts) ```typescript fields: [ { name: 'title', type: 'text', required: true }, { type: 'tabs', tabs: [ { label: 'Content', fields: [ { name: 'heroImage', type: 'upload', relationTo: 'media' }, { name: 'content', type: 'richText', required: true }, // ⭐ ADD excerpt HERE // ⭐ ADD ogImage HERE (after heroImage) ], }, { label: 'Meta', fields: [ { name: 'relatedPosts', type: 'relationship', admin: { position: 'sidebar' } }, { name: 'categories', type: 'relationship', admin: { position: 'sidebar' } }, // ⭐ ADD showInFooter HERE (with position: 'sidebar') ], }, { name: 'meta', label: 'SEO', fields: [/* SEO plugin fields including meta.image */], }, ], }, { name: 'publishedAt', type: 'date', admin: { position: 'sidebar' } }, { name: 'authors', type: 'relationship', admin: { position: 'sidebar' } }, { name: 'populatedAuthors', type: 'array', admin: { disabled: true, readOnly: true } }, ...slugField(), // ⭐ ADD status HERE (with position: 'sidebar') ] ``` ### Required Changes **1. Add excerpt field** (in Content tab, after content field): ```typescript { name: 'excerpt', type: 'text', label: '文章摘要', admin: { description: '顯示在文章列表頁,建議 150-200 字', multiline: true, }, maxLength: 200, } ``` **2. Add ogImage field** (in Content tab, after heroImage field): ```typescript { name: 'ogImage', type: 'upload', relationTo: 'media', label: '社群分享圖片', admin: { description: 'Facebook/LINE 分享時顯示的預覽圖,建議 1200x630px', }, } ``` **3. Add showInFooter field** (in Meta tab fields array): ```typescript { name: 'showInFooter', type: 'checkbox', label: '顯示在頁腳', defaultValue: false, admin: { position: 'sidebar', }, } ``` **4. Add status field** (at root level, after slugField()): ```typescript { name: 'status', type: 'select', label: '文章狀態', defaultValue: 'draft', options: [ { label: '草稿', value: 'draft' }, { label: '審核中', value: 'review' }, { label: '已發布', value: 'published' }, ], admin: { position: 'sidebar', }, } ``` ### Field Placement Summary | Field | Location | Notes | |-------|----------|-------| | excerpt | Content tab | After content field, multiline, maxLength: 200 | | ogImage | Content tab | After heroImage field | | showInFooter | Meta tab | position: 'sidebar' | | status | Root fields | position: 'sidebar' | ### Important Notes 1. **ogImage vs meta.image**: The SEO plugin provides `meta.image` for Open Graph, but this story adds a separate `ogImage` field specifically for social media sharing. They serve different purposes and both should be available. 2. **status vs _status**: Payload CMS has a built-in `_status` field (draft/published). The new `status` field is for custom workflow (draft → review → published) and is independent of the built-in draft system. 3. **Tab Structure**: The Posts collection uses tabs heavily. Ensure new fields are added to the correct tabs. ### File Structure ``` apps/backend/src/ └── collections/ └── Posts/ └── index.ts ← Modify this file (ADD 4 fields) ``` **No payload.config.ts changes needed** - collection already registered. ## Tasks / Subtasks - [x] **Task 1: Modify Posts/index.ts** (AC: 1, 2, 3, 4, 5) - [x] Add excerpt field in Content tab (after content) - [x] Add ogImage field in Content tab (after heroImage) - [x] Add showInFooter field in Meta tab - [x] Add status field at root level - [x] Verify all fields in correct locations - [x] **Task 2: Verify TypeScript types** (AC: 6) - [x] Run `pnpm build` in backend directory - [x] Check that payload-types.ts regenerates without errors - [x] Verify new fields are in TypeScript types - [x] **Task 3: Test Admin UI** (AC: 7) - [x] Start dev server: `pnpm dev` - [x] Login to Payload Admin - [x] Navigate to Posts collection - [x] Create test post with all new fields - [x] Verify excerpt accepts up to 200 characters - [x] Verify ogImage upload works - [x] Verify showInFooter checkbox works - [x] Verify status dropdown shows all options - [x] Verify sidebar position for showInFooter and status - [x] **Task 4: Verify Blog Integration** - [x] Test that excerpt displays in article list - [x] Test that ogImage is used for social sharing - [x] Test that status filtering works (draft/review/published) - [x] Test that showInFooter filters footer articles ## Testing Requirements ### Unit Tests ```typescript // apps/backend/src/collections/Posts/__tests__/Posts.spec.ts describe('Posts Collection - New Fields', () => { it('should have excerpt field', () => { const excerptField = Posts.fields.find(f => f.name === 'excerpt') expect(excerptField).toBeDefined() expect(excerptField?.maxLength).toBe(200) }) it('should have ogImage field', () => { const ogImageField = Posts.fields.find(f => f.name === 'ogImage') expect(ogImageField).toBeDefined() expect(ogImageField?.type).toBe('upload') }) it('should have showInFooter field', () => { const showInFooterField = Posts.fields.find(f => f.name === 'showInFooter') expect(showInFooterField).toBeDefined() expect(showInFooterField?.type).toBe('checkbox') expect(showInFooterField?.defaultValue).toBe(false) }) it('should have status field with correct options', () => { const statusField = Posts.fields.find(f => f.name === 'status') expect(statusField).toBeDefined() expect(statusField?.type).toBe('select') expect(statusField?.options).toHaveLength(3) expect(statusField?.defaultValue).toBe('draft') }) }) ``` ### Manual Testing Checklist - [ ] excerpt field is multiline textarea - [ ] excerpt field limits to 200 characters - [ ] excerpt shows description text - [ ] ogImage uploads to media collection - [ ] ogImage shows description about 1200x630px - [ ] showInFooter checkbox works - [ ] showInFooter default is unchecked - [ ] showInFooter appears in sidebar - [ ] status dropdown shows 3 options - [ ] status default is "草稿" - [ ] status appears in sidebar - [ ] Can save post with all new fields - [ ] Can edit post and change values ## Risk Assessment | Risk | Probability | Impact | Mitigation | |------|------------|--------|------------| | Tab structure confusion | Low | Medium | Follow exact tab placement instructions | | ogImage vs meta.image confusion | Low | Low | Document difference clearly | | status vs _status confusion | Low | Low | Document independence from built-in field | | maxLength not working | Low | Low | Test with >200 character input | ## Definition of Done - [ ] All 4 new fields added to Posts/index.ts - [ ] Fields placed in correct tabs - [ ] TypeScript types generate successfully - [ ] Admin UI functional with all fields - [ ] Unit tests pass - [ ] Code follows existing patterns - [ ] No linting errors - [ ] sprint-status.yaml updated to mark story as ready-for-dev ## Dev Agent Record ### Agent Model Used *To be filled by Dev Agent* ### Debug Log References *To be filled by Dev Agent* ### Completion Notes *To be filled by Dev Agent* ### File List *To be filled by Dev Agent* ## Change Log | Date | Action | Author | |------|--------|--------| | 2026-01-31 | Story created (Draft) | SM Agent (Bob) |