| Refreshing `/create` resets to the wrong creation tab | `src/app/create/page.tsx` | Active tab should persist in `miaojing:create-active-tab` and mirror to `/create?type=...`. Verify all creation tabs (`text2img`, `img2img`, `text2video`, `img2video`, `reversePrompt`) restore after refresh and query-param links still override storage. |
| 手机端创作提示词输入框没有固定在底部,或固定后遮住提示词/参考图/任务状态 | `src/components/create/mobile-creation-composer.tsx`, `src/app/globals.css`, `src/components/navbar.tsx`, `scripts/test-mobile-create-ui-policy.mjs` | Mobile composer should be `position: fixed` and should use `ResizeObserver` to publish `--create-mobile-composer-height` to `.create-chat-layout`; `.create-chat-thread` must reserve that measured height through `padding-bottom`. The mobile bottom nav must be rendered outside the sticky header, because a sticky/backdrop-filter header can trap fixed children and make the nav appear near the top instead of the viewport bottom. Run `node --no-warnings ./scripts/test-mobile-create-ui-policy.mjs` and verify a mobile viewport such as 390x844. |
| Create button is disabled while another task is still running, or active job cards overflow horizontally | `src/components/create/text-to-image.tsx`, `src/components/create/image-to-image.tsx`, `src/components/create/text-to-video.tsx`, `src/components/create/image-to-video.tsx`, `src/components/create/generation-task-list.tsx` | Create panels should keep submit enabled whenever models are available so users can start a different task while previous tasks run. Identical in-flight submissions are still blocked by `activeSubmissionSignaturesRef`. Active job cards should render inside the results column with wrapping vertical growth, not outside the result area. |
| User cannot cancel a queued/running generation task, or a cancelled task still writes history | `src/components/create/generation-task-list.tsx`, `src/lib/generation-job-client.ts`, `src/app/api/generation-jobs/[id]/route.ts`, `src/lib/generation-job-worker.ts`, create panel component | Task cards should pass `onCancelTask`, the client should call `cancelGenerationJob`, and `PATCH /api/generation-jobs/[id]` should set `status='cancelled'`. The worker must check the job is still `running` before charging credits, persisting history, or updating success/failure so late upstream responses do not resurrect cancelled jobs. |
| Earlier completed image tasks disappear while later tasks are still running | `src/components/create/text-to-image.tsx`, `src/components/create/image-to-image.tsx`, `src/components/create/generation-task-list.tsx` | The results column must not be a single `generating ? taskList : results` branch. Render active task cards and completed result cards together, and append each task's images as soon as that task succeeds instead of waiting for all submitted tasks to settle. |
@@ -10,7 +10,7 @@ Use this document to jump directly to code before broad searching.
| --- | --- | --- |
| Root layout and providers | `src/app/layout.tsx`, `src/components/app-shell.tsx`, `src/app/globals.css` | App shell wires navbar, site config sync, visit tracking, theme/account sync, toaster, full-width page mounting, and transient scrollbar visibility. Keep product content at the original component scale; use centered responsive containers instead of stretching all content to viewport edges. Global scrollbars are hidden by default and briefly show the rounded glass style when wheel/touch scrolling adds `scrollbars-visible` on `<html>`. |
| Home page | `src/app/page.tsx` | Landing/dashboard-like public entry. Check site config dependencies when changing brand text. |
| Navbar | `src/components/navbar.tsx`, `src/components/site-brand.tsx` | Navigation, brand display, auth-aware links. User-facing nav should not include removed feature routes. Logged-in desktop/mobile user buttons should show `AuthUser.avatarUrl` first and fall back to the display nickname initial only when the avatar is missing or fails to load. Avoid eager all-route `router.prefetch(...)` from the navbar; on production web it can compete with visible route/resource loading. The initial logged-in profile refresh is idle and one-shot so it does not compete with the first route transition. |
| Navbar | `src/components/navbar.tsx`, `src/components/site-brand.tsx` | Navigation, brand display, auth-aware links. User-facing nav should not include removed feature routes. Logged-in desktop/mobile user buttons should show `AuthUser.avatarUrl` first and fall back to the display nickname initial only when the avatar is missing or fails to load. Avoid eager all-route `router.prefetch(...)` from the navbar; on production web it can compete with visible route/resource loading. The initial logged-in profile refresh is idle and one-shot so it does not compete with the first route transition. Mobile bottom navigation must render as a sibling after the sticky header, not inside it, because header backdrop/filter contexts can trap `position: fixed` children away from the viewport bottom. |
| Footer | `src/components/site-footer.tsx` | Uses site config for policy/help/about links and filing text; footer background spans browser width while inner content keeps the original `max-w-7xl` scale. |
| Announcement popup | `src/components/announcement-popup.tsx`, `src/app/api/announcements/route.ts`, `src/app/globals.css` | Frontend popup behavior plus backend announcement CRUD. Desktop dialog is intentionally wide (`max-w-5xl`) for long Markdown notices; scrollbar styling is inherited from the global glass scrollbar rules. |
| Site config sync | `src/components/site-config-sync.tsx`, `src/lib/site-config.ts`, `src/app/api/site-config/route.ts` | Site name, tab title, logo, favicon, policy Markdown, filing, membership switch. `useSiteConfig()` keeps a shared browser snapshot, skips network refresh while that snapshot is still within the 5-minute TTL, and reuses the in-flight `/api/site-config` refresh so global consumers such as navbar, footer, site-brand, and policy pages do not each create their own concurrent config request during navigation. The API route runs legacy schema/default compatibility checks once per server process and retries on failure. |
@@ -49,14 +49,14 @@ Use this document to jump directly to code before broad searching.
| Feature | Primary Files | Server/API Files |
| --- | --- | --- |
| Tab container | `src/app/create/page.tsx` | Owns the five creation tabs. Active tab is persisted in localStorage and mirrored to `/create?type=...`, so refreshes and shared links stay on text-to-image, image-to-image, text-to-video, image-to-video, or reverse-prompt. Keep the five primary creation panels statically imported in this page: production users switch between these modes constantly, and `ssr:false` dynamic splitting adds visible chunk waits and fallback flashes on direct web access. On phones the mode switch is the single fixed icon row below the navbar; the page title and duplicate text mode strip are hidden. Mobile layout classes in this page and `src/app/globals.css` turn the create center into a chat-style flow for all five modes: history/status cards render above the fixed composer, hidden desktop result/history regions should not keep loading large media, and `src/components/create/mobile-creation-composer.tsx` owns the bottom send surface with compact params, optional styles/references, prompt or custom input, and right send button. |
| Tab container | `src/app/create/page.tsx` | Owns the five creation tabs. Active tab is persisted in localStorage and mirrored to `/create?type=...`, so refreshes and shared links stay on text-to-image, image-to-image, text-to-video, image-to-video, or reverse-prompt. Keep the five primary creation panels statically imported in this page: production users switch between these modes constantly, and `ssr:false` dynamic splitting adds visible chunk waits and fallback flashes on direct web access. On phones the mode switch is the single fixed icon row below the navbar; the page title and duplicate text mode strip are hidden. Mobile layout classes in this page and `src/app/globals.css` turn the create center into a chat-style flow for all five modes: history/status cards render above the fixed composer, hidden desktop result/history regions should not keep loading large media, and `src/components/create/mobile-creation-composer.tsx` owns the bottom send surface with compact params, optional styles/references, prompt or custom input, and right send button. The mobile thread reserves the measured composer height through `--create-mobile-composer-height` so fixed-bottom input does not cover prompts or previews. |
| Text to image | `src/components/create/text-to-image.tsx` | `src/app/api/generation-jobs/route.ts`, `src/app/api/generate/image/route.ts`, `src/components/create/use-generation-job-recovery.ts`. The create button remains available while other active tasks run; duplicate in-flight submissions are still blocked by `activeSubmissionSignaturesRef`. Active jobs render through `src/components/create/generation-task-list.tsx` inside the results column and expose a cancel action that calls `PATCH /api/generation-jobs/[id]`. Model select items use `src/components/create/grouped-model-select-items.tsx` so admin global system models appear under `默认模型` and user-added keys appear under `自定义模型`. Selected model capabilities from `src/lib/model-capabilities.ts` can hide unsupported aspect ratio/resolution/format/quality controls as well as filter their options, which is required for built-in 元界 image templates such as GPT Image 2 where the docs expose `size` pixel values instead of a separate aspect-ratio control. It consumes reuse drafts from `src/lib/creation-reuse.ts` and opens `src/components/create/inspiration-gallery-dialog.tsx` from the `获取灵感` action so gallery text-to-image works can fill prompt, negative prompt, model, ratio, resolution, format, quality, count, style, and guidance into the form. The create-panel history hook must stay scoped, e.g. `useCreationHistory({ mode: 'text2img', limit: 60 })`, so opening `/create` does not download the user's full history payload. The mobile conversation history should only mount on mobile viewports; CSS-hidden mobile history still runs image effects if mounted on desktop. |
| Image to image | `src/components/create/image-to-image.tsx`, `src/components/create/reference-image-mention-controls.tsx`, `src/components/reference-preview-image.tsx` | `src/app/api/generation-jobs/route.ts`, `src/app/api/generate/image/route.ts`, `src/components/create/use-generation-job-recovery.ts`, `src/lib/reference-image-prompt.ts`, `src/lib/reference-image-storage.ts`. Reference thumbnails single-click into a bare image overlay and use lightweight local preview rendering instead of painting the full uploaded data URL in every card. Active jobs render through `src/components/create/generation-task-list.tsx`, and the create button remains available while active tasks exist; identical in-flight submissions are still deduped. Model select items use `src/components/create/grouped-model-select-items.tsx` for `默认模型` versus `自定义模型` grouping. Selected model capabilities from `src/lib/model-capabilities.ts` can hide unsupported aspect ratio/resolution/format/quality controls as well as filter their options, which is required for built-in 元界 image templates such as GPT Image 2 where the docs expose `size` pixel values instead of a separate aspect-ratio control. 图生图 removes `自动` from ratio/resolution/count controls, defaults count to `1`, and derives ratio from Yuanjie size labels or dimensions when the selected model hides the separate ratio control. It consumes reuse drafts from `src/lib/creation-reuse.ts` and opens `src/components/create/inspiration-gallery-dialog.tsx` from the `获取灵感` action so gallery image-to-image works can place reference images and fill prompt, negative prompt, model, ratio, resolution, format, quality, count, style, and strength into the form. 多参考图会显示 `@参考图1` 等标签,提示词输入框输入 `@` 可选择参考图,提交时发送 `referenceImageAnnotations`,后端把 token 与上传顺序、文件名、尺寸写入上游 prompt;创作历史会把 data URL/远程参考图持久化到 `works/references` 并写入 `referenceImageThumbnails`,分享到画廊会携带所有参考图和标注。 Mobile uses the fixed composer with an upload/reference thumbnail strip, mention-aware prompt input, compact ratio/resolution/format/count controls, and a mobile-only status/history flow. |
| Text to video | `src/components/create/text-to-video.tsx` | `src/app/api/generation-jobs/route.ts`, `src/app/api/generate/video/route.ts`, `src/components/create/use-generation-job-recovery.ts`. The create button remains available while active tasks exist, active jobs render through `src/components/create/generation-task-list.tsx`, running tasks can be cancelled, and model select items use `src/components/create/grouped-model-select-items.tsx` for `默认模型` versus `自定义模型` grouping. It consumes video reuse drafts from `src/lib/creation-reuse.ts` and opens `src/components/create/inspiration-gallery-dialog.tsx` from the `获取灵感` action so gallery text-to-video works can fill prompt, negative prompt, model, ratio, duration, camera movement, and style. Mobile uses the fixed composer with compact ratio/duration/resolution/camera controls, a horizontal style strip, and thumbnail/placeholder history cards instead of preloading hidden desktop videos. |
| Image to video | `src/components/create/image-to-video.tsx`, `src/components/create/reference-image-mention-controls.tsx`, `src/components/reference-preview-image.tsx` | `src/app/api/generation-jobs/route.ts`, `src/app/api/generate/video/route.ts`, `src/components/create/use-generation-job-recovery.ts`, `src/lib/reference-image-prompt.ts`, `src/lib/reference-image-storage.ts`. Uploaded reference thumbnails single-click into the same bare image overlay used by image-to-image and use lightweight local preview rendering. Active jobs render through `src/components/create/generation-task-list.tsx`, the create button remains available while active tasks exist, and running tasks can be cancelled. Model select items use `src/components/create/grouped-model-select-items.tsx` for `默认模型` versus `自定义模型` grouping. It consumes video reuse drafts from `src/lib/creation-reuse.ts` and opens `src/components/create/inspiration-gallery-dialog.tsx` from the `获取灵感` action so gallery image-to-video works can place reference images and fill prompt, negative prompt, model, ratio, duration, and camera movement. 多参考图会显示 `@参考图1` 等标签,提示词输入框输入 `@` 可选择参考图,提交时发送 `referenceImageAnnotations`,后端把 token 与上传顺序、文件名、尺寸写入上游 prompt;创作历史会把 data URL/远程参考图持久化到 `works/references` 并写入 `referenceImageThumbnails`,分享到画廊会携带所有参考图和标注。 Mobile uses the fixed composer with an upload/reference thumbnail strip, mention-aware prompt input, compact video controls, and thumbnail/placeholder history cards instead of preloading hidden desktop videos. |
| Reverse prompt | `src/components/create/reverse-prompt-panel.tsx` | `src/app/api/generate/reverse-prompt/route.ts`, `src/app/api/generate/suggest-prompt/route.ts`, `src/lib/generation-job-client.ts`, `src/components/create/use-generation-job-recovery.ts`. Reverse prompt now runs as a background job, survives refresh/auth change/tab switch, and writes the completed result back into the normal creation history flow instead of relying on an optimistic local-only row. Mobile uses the fixed composer custom input slot for upload/change-image, prompt mode, language controls, and a mobile status/result/history flow. |
| Mobile creation composer | `src/components/create/mobile-creation-composer.tsx`, `src/app/globals.css` | Mobile-only fixed bottom composer used by all five creation panels to match chat-style clients: top parameter strip with compact dropdown buttons, optional reference/upload strip, optional style strip, default prompt input or a custom input slot, and right send button. The mobile creation center uses one 16px UI font size across selected values, style chips, composer input, and conversation prompts. The mobile text/image parameter strips hide long labels and remove `自动` from compact ratio/resolution/count choices where the panel already defaults to explicit values. The mobile style strip shows only one horizontal row when collapsed and expands upward for search/more presets after tapping `展开`; video styles use a horizontal chip strip. Mode selection stays only in the sticky header tabs. Desktop creation forms remain the source for full advanced controls, while mobile result/history/status flows mount only on mobile viewports. |
| Mobile creation composer | `src/components/create/mobile-creation-composer.tsx`, `src/app/globals.css` | Mobile-only fixed bottom composer used by all five creation panels to match chat-style clients: top parameter strip with compact dropdown buttons, optional reference/upload strip, optional style strip, default prompt input or a custom input slot, and right send button. It uses `ResizeObserver` to publish `--create-mobile-composer-height` on the nearest `.create-chat-layout`; `.create-chat-thread` must keep a matching bottom padding so the fixed composer stays at the bottom without covering prompts, references, or status cards. The mobile creation center uses one 16px UI font size across selected values, style chips, composer input, and conversation prompts. The mobile text/image parameter strips hide long labels and remove `自动` from compact ratio/resolution/count choices where the panel already defaults to explicit values. The mobile style strip shows only one horizontal row when collapsed and expands upward for search/more presets after tapping `展开`; video styles use a horizontal chip strip. Mode selection stays only in the sticky header tabs. Desktop creation forms remain the source for full advanced controls, while mobile result/history/status flows mount only on mobile viewports. |
| Image count input/dropdown | `src/components/create/image-count-combobox.tsx` | Shared compact count control for manual image count entry and common dropdown options. |
| Style presets | `src/components/create/style-preset-selector.tsx`, `src/lib/style-presets-client.ts`, `src/app/api/style-presets/route.ts`, `src/lib/style-preset-store.ts`, `src/lib/model-config.ts` | Style presets are stored in `image_style_presets`, seeded from defaults, sorted by `usage_count`, and incremented from image generation jobs. The selector exposes stable `.style-preset-selector` and `.style-preset-list` classes so mobile create CSS can show a one-row collapsed strip and an expanded list of at least several rows inside the bottom composer. |
| Loading/error panels | `src/components/create/generation-loading-panel.tsx`, `src/components/create/generation-task-list.tsx`, `src/components/create/generation-error-panel.tsx` | Shared generation status UI. `generation-task-list` keeps multiple active job cards constrained to the results column, exposes cancel buttons for normal active tasks, and image/video create panels render active tasks plus completed result cards together so earlier finished jobs do not disappear while later jobs keep running. |
assert.match(css,/\.create-mobile-shell\s*\{[^}]*height:\s*calc\(/,'mobile create shell should own the available viewport-height region');
assert.match(css,/\.create-chat-layout\s*\{[^}]*height:\s*100%/,'mobile create layout should fill the shell instead of growing under the composer');
assert.match(css,/\.create-chat-layout\s*\{[^}]*overflow:\s*hidden/,'mobile create layout should clip children to the conversation/composer split');
assert.match(css,/\.create-chat-thread\s*\{[^}]*overflow-y:\s*auto/,'mobile conversation thread should scroll independently above the composer');
assert.match(css,/\.create-mobile-dialog-composer\s*\{[^}]*position:\s*sticky/,'mobile composer should stay in layout flow instead of overlaying the conversation');
assert.doesNotMatch(css,/\.create-mobile-dialog-composer\s*\{[^}]*position:\s*fixed/,'mobile composer must not be fixed because it covers prompts and previews');
assert.match(css,/\.create-chat-thread\s*\{[^}]*padding-bottom:\s*calc\(var\(--create-mobile-composer-height/,'mobile conversation thread should reserve the measured composer height');
assert.match(css,/\.create-mobile-dialog-composer\s*\{[^}]*position:\s*fixed/,'mobile composer should stay fixed to the bottom like a chat input');
assert.doesNotMatch(css,/\.create-mobile-dialog-composer\s*\{[^}]*position:\s*sticky/,'mobile composer should not drift inside the thread layout');
assert.match(composerSource,/ResizeObserver/,'mobile composer should measure its height when params, styles, or references change');
assert.match(composerSource,/--create-mobile-composer-height/,'mobile composer should publish its measured height to the layout');
assert.doesNotMatch(css,/\.create-mobile-dialog-composer::before/,'mobile composer should not render the user screenshot annotation as a red divider');
assert.doesNotMatch(css,/rgb\(219 73 50/,'mobile create UI should not include a hard-coded red annotation line');
});
awaitrunTest('mobile bottom navigation is not trapped by the sticky header',()=>{
constsource=read('src/components/navbar.tsx');
assert.match(source,/return\s*\(\s*<>/,'Navbar should wrap the sticky header and fixed mobile nav as siblings');
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.