Skip to content

[codex] Add photo-driven candidate grid workflow - #1

Merged
similaitw merged 1 commit into
mainfrom
codex/photo-grid-v4
Jun 22, 2026
Merged

[codex] Add photo-driven candidate grid workflow#1
similaitw merged 1 commit into
mainfrom
codex/photo-grid-v4

Conversation

@similaitw

Copy link
Copy Markdown
Owner

What changed

  • add up to five local IndexedDB reference photos with rights confirmation and ZIP project backups
  • replace multiple generation tasks with one ChatGPT/Gemini project Markdown manifest
  • separate generated grid cells from LINE target count and add candidate selection
  • default to a 3×3 candidate grid with eight selected stickers
  • migrate v2/v3 projects to the v4 model while retaining v1 Markdown parsing

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

  • photos stay in browser storage and are only included in explicit project backups
  • reference images are uploaded separately with the Markdown so multimodal providers can read them reliably
  • Gemini provenance detection remains unchanged; no watermark removal path is added
  • LINE validation and animation export operate only on selected candidates

Validation

  • npm run typecheck
  • npm test — 19 tests
  • npm run build
  • npm run test:e2e — photo upload, Markdown download, ZIP backup/restore, and 9-to-8 candidate selection
  • npm audit --audit-level=high — 0 vulnerabilities

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
line_sticker Ready Ready Preview, Comment Jun 22, 2026 5:22am

@similaitw
similaitw marked this pull request as ready for review June 22, 2026 05:24
@similaitw
similaitw merged commit 61be55f into main Jun 22, 2026
3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/domain/project.ts
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 },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/domain/validation.ts
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} 張`});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 || '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant