[codex] Add photo-driven candidate grid workflow - #1
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18d0f23959
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const stickers = (value.stickers || []).map((asset, index) => ({ ...asset, provenanceMark: asset.provenanceMark ?? 'unknown' as const, | ||
| gridIndex: index, included: index < value.settings.count, selectedAt: index < value.settings.count ? index + 1 : undefined })); | ||
| const firstId = stickers[0]?.id; | ||
| return { ...base, name: value.name, settings: { ...base.settings, character: value.settings.character, count: value.settings.count, columns: value.settings.columns, padding: value.settings.padding, fontSize: value.settings.fontSize, loops: value.settings.loops }, |
There was a problem hiding this comment.
Compute legacy v2 grid rows during migration
When a v2 project used the old default layout (8 stickers with 4 columns), this keeps base.settings.rows at the new default of 3 while copying columns: 4, so the migrated project becomes a 12-cell grid. Validation now requires captions and stickers to match rows * columns, and re-slicing the saved sheet will crop it as 3 rows instead of the original 2, making previously valid legacy projects fail until users regenerate or manually rebuild them. Derive rows from the legacy count/columns and pad captions to that cell count.
Useful? React with 👍 / 👎.
| function reorder(index: number, offset: number) { const target = index + offset; if (target < 0 || target >= project.referencePhotos.length) return; | ||
| const next = [...project.referencePhotos]; [next[index], next[target]] = [next[target], next[index]]; | ||
| dispatch({ type: 'update', patch: { referencePhotos: next.map((item, order) => ({ ...item, order })) } }); } | ||
| async function remove(id: string) { await removeReferencePhoto(id); const next = project.referencePhotos.filter((item) => item.id !== id).map((item, order) => ({ ...item, order })); |
There was a problem hiding this comment.
Preserve photo blobs when removal can be undone
If a user removes a reference photo and then clicks the toolbar undo button, the reducer restores the previous referencePhotos metadata but this line has already deleted the blob from IndexedDB. The restored project then shows a missing photo and later MD download, project backup, or export paths fail their getReferencePhoto checks with “找不到參考照片”. Defer physical deletion until it cannot be restored, or make undo restore the blob as well.
Useful? React with 👍 / 👎.
| if(project.stickers.length!==project.settings.count)issues.push({level:'error',code:'ASSET_COUNT',message:`貼圖須為 ${project.settings.count} 張,目前 ${project.stickers.length} 張`}); | ||
| if(project.settings.rows<2||project.settings.rows>8||project.settings.columns<2||project.settings.columns>8||cellCount<project.settings.count)issues.push({level:'error',code:'GRID',message:'生成網格須為 2–8 行列,且容量不可小於 LINE 入選張數'}); | ||
| if(project.captionSlots.length!==cellCount)issues.push({level:'error',code:'CAPTION_COUNT',message:`文字槽位須剛好 ${cellCount} 個,目前 ${project.captionSlots.length} 個`}); | ||
| if(project.stickers.length!==cellCount)issues.push({level:'error',code:'CANDIDATE_COUNT',message:`候選素材須為 ${cellCount} 張,目前 ${project.stickers.length} 張`}); |
There was a problem hiding this comment.
Allow exporting after deleting unused candidates
With the new candidate workflow, a user can generate the default 3×3 sheet, keep the required 8 selected stickers, and delete the extra unselected candidate from the results grid. This check still requires all 9 candidate cells to remain, so validation blocks an otherwise complete LINE export even though buildStickerZip and the rest of validation operate only on selected assets. Gate export on the selected count/assets rather than the total candidate count, or only apply this as a non-blocking prompt before re-slicing.
Useful? React with 👍 / 👎.
| export function Timeline() { | ||
| const { project, setAnimationFrames } = useProject(); const spec = getSpec(project.type); | ||
| const [activeId, setActiveId] = useState(''); const stickerId = activeId || project.stickers[0]?.id || ''; | ||
| const selectedStickers = project.stickers.filter((asset) => asset.included); const [activeId, setActiveId] = useState(''); const stickerId = activeId || selectedStickers[0]?.id || ''; |
There was a problem hiding this comment.
Fall back when the active animated sticker is unselected
For animated/popup/effect projects, if a user selects a sticker in the timeline and then removes it from the selected candidates, activeId still wins here even though it is no longer present in selectedStickers. The dropdown then has no matching option while uploads continue to attach frames to the unselected sticker, so validation/export still fail for the newly selected replacement that has no frames. Ignore activeId unless it is still included, or reset it when candidate selection changes.
Useful? React with 👍 / 👎.
What changed
Why
Creators can now turn their own photos into a consistent sticker character, upload one complete instruction file, and choose the best LINE-compliant subset from a configurable grid.
Safety and compatibility
Validation
npm run typechecknpm test— 19 testsnpm run buildnpm run test:e2e— photo upload, Markdown download, ZIP backup/restore, and 9-to-8 candidate selectionnpm audit --audit-level=high— 0 vulnerabilities